/**
 * dat-gui JavaScript Controller Library
 * http://code.google.com/p/dat-gui
 *
 * Copyright 2011 Data Arts Team, Google Creative Lab
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 */

function ___$insertStyle(css) {
  if (!css) {
    return;
  }
  if (typeof window === 'undefined') {
    return;
  }

  var style = document.createElement('style');

  style.setAttribute('type', 'text/css');
  style.innerHTML = css;
  document.head.appendChild(style);

  return css;
}

function colorToString (color, forceCSSHex) {
  var colorFormat = color.__state.conversionName.toString();
  var r = Math.round(color.r);
  var g = Math.round(color.g);
  var b = Math.round(color.b);
  var a = color.a;
  var h = Math.round(color.h);
  var s = color.s.toFixed(1);
  var v = color.v.toFixed(1);
  if (forceCSSHex || colorFormat === 'THREE_CHAR_HEX' || colorFormat === 'SIX_CHAR_HEX') {
    var str = color.hex.toString(16);
    while (str.length < 6) {
      str = '0' + str;
    }
    return '#' + str;
  } else if (colorFormat === 'CSS_RGB') {
    return 'rgb(' + r + ',' + g + ',' + b + ')';
  } else if (colorFormat === 'CSS_RGBA') {
    return 'rgba(' + r + ',' + g + ',' + b + ',' + a + ')';
  } else if (colorFormat === 'HEX') {
    return '0x' + color.hex.toString(16);
  } else if (colorFormat === 'RGB_ARRAY') {
    return '[' + r + ',' + g + ',' + b + ']';
  } else if (colorFormat === 'RGBA_ARRAY') {
    return '[' + r + ',' + g + ',' + b + ',' + a + ']';
  } else if (colorFormat === 'RGB_OBJ') {
    return '{r:' + r + ',g:' + g + ',b:' + b + '}';
  } else if (colorFormat === 'RGBA_OBJ') {
    return '{r:' + r + ',g:' + g + ',b:' + b + ',a:' + a + '}';
  } else if (colorFormat === 'HSV_OBJ') {
    return '{h:' + h + ',s:' + s + ',v:' + v + '}';
  } else if (colorFormat === 'HSVA_OBJ') {
    return '{h:' + h + ',s:' + s + ',v:' + v + ',a:' + a + '}';
  }
  return 'unknown format';
}

var ARR_EACH = Array.prototype.forEach;
var ARR_SLICE = Array.prototype.slice;
var Common = {
  BREAK: {},
  extend: function extend(target) {
    this.each(ARR_SLICE.call(arguments, 1), function (obj) {
      var keys = this.isObject(obj) ? Object.keys(obj) : [];
      keys.forEach(function (key) {
        if (!this.isUndefined(obj[key])) {
          target[key] = obj[key];
        }
      }.bind(this));
    }, this);
    return target;
  },
  defaults: function defaults(target) {
    this.each(ARR_SLICE.call(arguments, 1), function (obj) {
      var keys = this.isObject(obj) ? Object.keys(obj) : [];
      keys.forEach(function (key) {
        if (this.isUndefined(target[key])) {
          target[key] = obj[key];
        }
      }.bind(this));
    }, this);
    return target;
  },
  compose: function compose() {
    var toCall = ARR_SLICE.call(arguments);
    return function () {
      var args = ARR_SLICE.call(arguments);
      for (var i = toCall.length - 1; i >= 0; i--) {
        args = [toCall[i].apply(this, args)];
      }
      return args[0];
    };
  },
  each: function each(obj, itr, scope) {
    if (!obj) {
      return;
    }
    if (ARR_EACH && obj.forEach && obj.forEach === ARR_EACH) {
      obj.forEach(itr, scope);
    } else if (obj.length === obj.length + 0) {
      var key = void 0;
      var l = void 0;
      for (key = 0, l = obj.length; key < l; key++) {
        if (key in obj && itr.call(scope, obj[key], key) === this.BREAK) {
          return;
        }
      }
    } else {
      for (var _key in obj) {
        if (itr.call(scope, obj[_key], _key) === this.BREAK) {
          return;
        }
      }
    }
  },
  defer: function defer(fnc) {
    setTimeout(fnc, 0);
  },
  debounce: function debounce(func, threshold, callImmediately) {
    var timeout = void 0;
    return function () {
      var obj = this;
      var args = arguments;
      function delayed() {
        timeout = null;
        if (!callImmediately) func.apply(obj, args);
      }
      var callNow = callImmediately || !timeout;
      clearTimeout(timeout);
      timeout = setTimeout(delayed, threshold);
      if (callNow) {
        func.apply(obj, args);
      }
    };
  },
  toArray: function toArray(obj) {
    if (obj.toArray) return obj.toArray();
    return ARR_SLICE.call(obj);
  },
  isUndefined: function isUndefined(obj) {
    return obj === undefined;
  },
  isNull: function isNull(obj) {
    return obj === null;
  },
  isNaN: function (_isNaN) {
    function isNaN(_x) {
      return _isNaN.apply(this, arguments);
    }
    isNaN.toString = function () {
      return _isNaN.toString();
    };
    return isNaN;
  }(function (obj) {
    return isNaN(obj);
  }),
  isArray: Array.isArray || function (obj) {
    return obj.constructor === Array;
  },
  isObject: function isObject(obj) {
    return obj === Object(obj);
  },
  isNumber: function isNumber(obj) {
    return obj === obj + 0;
  },
  isString: function isString(obj) {
    return obj === obj + '';
  },
  isBoolean: function isBoolean(obj) {
    return obj === false || obj === true;
  },
  isFunction: function isFunction(obj) {
    return Object.prototype.toString.call(obj) === '[object Function]';
  }
};

var INTERPRETATIONS = [
{
  litmus: Common.isString,
  conversions: {
    THREE_CHAR_HEX: {
      read: function read(original) {
        var test = original.match(/^#([A-F0-9])([A-F0-9])([A-F0-9])$/i);
        if (test === null) {
          return false;
        }
        return {
          space: 'HEX',
          hex: parseInt('0x' + test[1].toString() + test[1].toString() + test[2].toString() + test[2].toString() + test[3].toString() + test[3].toString(), 0)
        };
      },
      write: colorToString
    },
    SIX_CHAR_HEX: {
      read: function read(original) {
        var test = original.match(/^#([A-F0-9]{6})$/i);
        if (test === null) {
          return false;
        }
        return {
          space: 'HEX',
          hex: parseInt('0x' + test[1].toString(), 0)
        };
      },
      write: colorToString
    },
    CSS_RGB: {
      read: function read(original) {
        var test = original.match(/^rgb\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/);
        if (test === null) {
          return false;
        }
        return {
          space: 'RGB',
          r: parseFloat(test[1]),
          g: parseFloat(test[2]),
          b: parseFloat(test[3])
        };
      },
      write: colorToString
    },
    CSS_RGBA: {
      read: function read(original) {
        var test = original.match(/^rgba\(\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*,\s*(.+)\s*\)/);
        if (test === null) {
          return false;
        }
        return {
          space: 'RGB',
          r: parseFloat(test[1]),
          g: parseFloat(test[2]),
          b: parseFloat(test[3]),
          a: parseFloat(test[4])
        };
      },
      write: colorToString
    }
  }
},
{
  litmus: Common.isNumber,
  conversions: {
    HEX: {
      read: function read(original) {
        return {
          space: 'HEX',
          hex: original,
          conversionName: 'HEX'
        };
      },
      write: function write(color) {
        return color.hex;
      }
    }
  }
},
{
  litmus: Common.isArray,
  conversions: {
    RGB_ARRAY: {
      read: function read(original) {
        if (original.length !== 3) {
          return false;
        }
        return {
          space: 'RGB',
          r: original[0],
          g: original[1],
          b: original[2]
        };
      },
      write: function write(color) {
        return [color.r, color.g, color.b];
      }
    },
    RGBA_ARRAY: {
      read: function read(original) {
        if (original.length !== 4) return false;
        return {
          space: 'RGB',
          r: original[0],
          g: original[1],
          b: original[2],
          a: original[3]
        };
      },
      write: function write(color) {
        return [color.r, color.g, color.b, color.a];
      }
    }
  }
},
{
  litmus: Common.isObject,
  conversions: {
    RGBA_OBJ: {
      read: function read(original) {
        if (Common.isNumber(original.r) && Common.isNumber(original.g) && Common.isNumber(original.b) && Common.isNumber(original.a)) {
          return {
            space: 'RGB',
            r: original.r,
            g: original.g,
            b: original.b,
            a: original.a
          };
        }
        return false;
      },
      write: function write(color) {
        return {
          r: color.r,
          g: color.g,
          b: color.b,
          a: color.a
        };
      }
    },
    RGB_OBJ: {
      read: function read(original) {
        if (Common.isNumber(original.r) && Common.isNumber(original.g) && Common.isNumber(original.b)) {
          return {
            space: 'RGB',
            r: original.r,
            g: original.g,
            b: original.b
          };
        }
        return false;
      },
      write: function write(color) {
        return {
          r: color.r,
          g: color.g,
          b: color.b
        };
      }
    },
    HSVA_OBJ: {
      read: function read(original) {
        if (Common.isNumber(original.h) && Common.isNumber(original.s) && Common.isNumber(original.v) && Common.isNumber(original.a)) {
          return {
            space: 'HSV',
            h: original.h,
            s: original.s,
            v: original.v,
            a: original.a
          };
        }
        return false;
      },
      write: function write(color) {
        return {
          h: color.h,
          s: color.s,
          v: color.v,
          a: color.a
        };
      }
    },
    HSV_OBJ: {
      read: function read(original) {
        if (Common.isNumber(original.h) && Common.isNumber(original.s) && Common.isNumber(original.v)) {
          return {
            space: 'HSV',
            h: original.h,
            s: original.s,
            v: original.v
          };
        }
        return false;
      },
      write: function write(color) {
        return {
          h: color.h,
          s: color.s,
          v: color.v
        };
      }
    }
  }
}];
var result = void 0;
var toReturn = void 0;
var interpret = function interpret() {
  toReturn = false;
  var original = arguments.length > 1 ? Common.toArray(arguments) : arguments[0];
  Common.each(INTERPRETATIONS, function (family) {
    if (family.litmus(original)) {
      Common.each(family.conversions, function (conversion, conversionName) {
        result = conversion.read(original);
        if (toReturn === false && result !== false) {
          toReturn = result;
          result.conversionName = conversionName;
          result.conversion = conversion;
          return Common.BREAK;
        }
      });
      return Common.BREAK;
    }
  });
  return toReturn;
};

var tmpComponent = void 0;
var ColorMath = {
  hsv_to_rgb: function hsv_to_rgb(h, s, v) {
    var hi = Math.floor(h / 60) % 6;
    var f = h / 60 - Math.floor(h / 60);
    var p = v * (1.0 - s);
    var q = v * (1.0 - f * s);
    var t = v * (1.0 - (1.0 - f) * s);
    var c = [[v, t, p], [q, v, p], [p, v, t], [p, q, v], [t, p, v], [v, p, q]][hi];
    return {
      r: c[0] * 255,
      g: c[1] * 255,
      b: c[2] * 255
    };
  },
  rgb_to_hsv: function rgb_to_hsv(r, g, b) {
    var min = Math.min(r, g, b);
    var max = Math.max(r, g, b);
    var delta = max - min;
    var h = void 0;
    var s = void 0;
    if (max !== 0) {
      s = delta / max;
    } else {
      return {
        h: NaN,
        s: 0,
        v: 0
      };
    }
    if (r === max) {
      h = (g - b) / delta;
    } else if (g === max) {
      h = 2 + (b - r) / delta;
    } else {
      h = 4 + (r - g) / delta;
    }
    h /= 6;
    if (h < 0) {
      h += 1;
    }
    return {
      h: h * 360,
      s: s,
      v: max / 255
    };
  },
  rgb_to_hex: function rgb_to_hex(r, g, b) {
    var hex = this.hex_with_component(0, 2, r);
    hex = this.hex_with_component(hex, 1, g);
    hex = this.hex_with_component(hex, 0, b);
    return hex;
  },
  component_from_hex: function component_from_hex(hex, componentIndex) {
    return hex >> componentIndex * 8 & 0xFF;
  },
  hex_with_component: function hex_with_component(hex, componentIndex, value) {
    return value << (tmpComponent = componentIndex * 8) | hex & ~(0xFF << tmpComponent);
  }
};

var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) {
  return typeof obj;
} : function (obj) {
  return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj;
};











var classCallCheck = function (instance, Constructor) {
  if (!(instance instanceof Constructor)) {
    throw new TypeError("Cannot call a class as a function");
  }
};

var createClass = function () {
  function defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }

  return function (Constructor, protoProps, staticProps) {
    if (protoProps) defineProperties(Constructor.prototype, protoProps);
    if (staticProps) defineProperties(Constructor, staticProps);
    return Constructor;
  };
}();







var get = function get(object, property, receiver) {
  if (object === null) object = Function.prototype;
  var desc = Object.getOwnPropertyDescriptor(object, property);

  if (desc === undefined) {
    var parent = Object.getPrototypeOf(object);

    if (parent === null) {
      return undefined;
    } else {
      return get(parent, property, receiver);
    }
  } else if ("value" in desc) {
    return desc.value;
  } else {
    var getter = desc.get;

    if (getter === undefined) {
      return undefined;
    }

    return getter.call(receiver);
  }
};

var inherits = function (subClass, superClass) {
  if (typeof superClass !== "function" && superClass !== null) {
    throw new TypeError("Super expression must either be null or a function, not " + typeof superClass);
  }

  subClass.prototype = Object.create(superClass && superClass.prototype, {
    constructor: {
      value: subClass,
      enumerable: false,
      writable: true,
      configurable: true
    }
  });
  if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass;
};











var possibleConstructorReturn = function (self, call) {
  if (!self) {
    throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
  }

  return call && (typeof call === "object" || typeof call === "function") ? call : self;
};

var Color = function () {
  function Color() {
    classCallCheck(this, Color);
    this.__state = interpret.apply(this, arguments);
    if (this.__state === false) {
      throw new Error('Failed to interpret color arguments');
    }
    this.__state.a = this.__state.a || 1;
  }
  createClass(Color, [{
    key: 'toString',
    value: function toString() {
      return colorToString(this);
    }
  }, {
    key: 'toHexString',
    value: function toHexString() {
      return colorToString(this, true);
    }
  }, {
    key: 'toOriginal',
    value: function toOriginal() {
      return this.__state.conversion.write(this);
    }
  }]);
  return Color;
}();
function defineRGBComponent(target, component, componentHexIndex) {
  Object.defineProperty(target, component, {
    get: function get$$1() {
      if (this.__state.space === 'RGB') {
        return this.__state[component];
      }
      Color.recalculateRGB(this, component, componentHexIndex);
      return this.__state[component];
    },
    set: function set$$1(v) {
      if (this.__state.space !== 'RGB') {
        Color.recalculateRGB(this, component, componentHexIndex);
        this.__state.space = 'RGB';
      }
      this.__state[component] = v;
    }
  });
}
function defineHSVComponent(target, component) {
  Object.defineProperty(target, component, {
    get: function get$$1() {
      if (this.__state.space === 'HSV') {
        return this.__state[component];
      }
      Color.recalculateHSV(this);
      return this.__state[component];
    },
    set: function set$$1(v) {
      if (this.__state.space !== 'HSV') {
        Color.recalculateHSV(this);
        this.__state.space = 'HSV';
      }
      this.__state[component] = v;
    }
  });
}
Color.recalculateRGB = function (color, component, componentHexIndex) {
  if (color.__state.space === 'HEX') {
    color.__state[component] = ColorMath.component_from_hex(color.__state.hex, componentHexIndex);
  } else if (color.__state.space === 'HSV') {
    Common.extend(color.__state, ColorMath.hsv_to_rgb(color.__state.h, color.__state.s, color.__state.v));
  } else {
    throw new Error('Corrupted color state');
  }
};
Color.recalculateHSV = function (color) {
  var result = ColorMath.rgb_to_hsv(color.r, color.g, color.b);
  Common.extend(color.__state, {
    s: result.s,
    v: result.v
  });
  if (!Common.isNaN(result.h)) {
    color.__state.h = result.h;
  } else if (Common.isUndefined(color.__state.h)) {
    color.__state.h = 0;
  }
};
Color.COMPONENTS = ['r', 'g', 'b', 'h', 's', 'v', 'hex', 'a'];
defineRGBComponent(Color.prototype, 'r', 2);
defineRGBComponent(Color.prototype, 'g', 1);
defineRGBComponent(Color.prototype, 'b', 0);
defineHSVComponent(Color.prototype, 'h');
defineHSVComponent(Color.prototype, 's');
defineHSVComponent(Color.prototype, 'v');
Object.defineProperty(Color.prototype, 'a', {
  get: function get$$1() {
    return this.__state.a;
  },
  set: function set$$1(v) {
    this.__state.a = v;
  }
});
Object.defineProperty(Color.prototype, 'hex', {
  get: function get$$1() {
    if (!this.__state.space !== 'HEX') {
      this.__state.hex = ColorMath.rgb_to_hex(this.r, this.g, this.b);
    }
    return this.__state.hex;
  },
  set: function set$$1(v) {
    this.__state.space = 'HEX';
    this.__state.hex = v;
  }
});

var Controller = function () {
  function Controller(object, property) {
    classCallCheck(this, Controller);
    this.initialValue = object[property];
    this.domElement = document.createElement('div');
    this.object = object;
    this.property = property;
    this.__onChange = undefined;
    this.__onFinishChange = undefined;
  }
  createClass(Controller, [{
    key: 'onChange',
    value: function onChange(fnc) {
      this.__onChange = fnc;
      return this;
    }
  }, {
    key: 'onFinishChange',
    value: function onFinishChange(fnc) {
      this.__onFinishChange = fnc;
      return this;
    }
  }, {
    key: 'setValue',
    value: function setValue(newValue) {
      this.object[this.property] = newValue;
      if (this.__onChange) {
        this.__onChange.call(this, newValue);
      }
      this.updateDisplay();
      return this;
    }
  }, {
    key: 'getValue',
    value: function getValue() {
      return this.object[this.property];
    }
  }, {
    key: 'updateDisplay',
    value: function updateDisplay() {
      return this;
    }
  }, {
    key: 'isModified',
    value: function isModified() {
      return this.initialValue !== this.getValue();
    }
  }]);
  return Controller;
}();

var EVENT_MAP = {
  HTMLEvents: ['change'],
  MouseEvents: ['click', 'mousemove', 'mousedown', 'mouseup', 'mouseover'],
  KeyboardEvents: ['keydown']
};
var EVENT_MAP_INV = {};
Common.each(EVENT_MAP, function (v, k) {
  Common.each(v, function (e) {
    EVENT_MAP_INV[e] = k;
  });
});
var CSS_VALUE_PIXELS = /(\d+(\.\d+)?)px/;
function cssValueToPixels(val) {
  if (val === '0' || Common.isUndefined(val)) {
    return 0;
  }
  var match = val.match(CSS_VALUE_PIXELS);
  if (!Common.isNull(match)) {
    return parseFloat(match[1]);
  }
  return 0;
}
var dom = {
  makeSelectable: function makeSelectable(elem, selectable) {
    if (elem === undefined || elem.style === undefined) return;
    elem.onselectstart = selectable ? function () {
      return false;
    } : function () {};
    elem.style.MozUserSelect = selectable ? 'auto' : 'none';
    elem.style.KhtmlUserSelect = selectable ? 'auto' : 'none';
    elem.unselectable = selectable ? 'on' : 'off';
  },
  makeFullscreen: function makeFullscreen(elem, hor, vert) {
    var vertical = vert;
    var horizontal = hor;
    if (Common.isUndefined(horizontal)) {
      horizontal = true;
    }
    if (Common.isUndefined(vertical)) {
      vertical = true;
    }
    elem.style.position = 'absolute';
    if (horizontal) {
      elem.style.left = 0;
      elem.style.right = 0;
    }
    if (vertical) {
      elem.style.top = 0;
      elem.style.bottom = 0;
    }
  },
  fakeEvent: function fakeEvent(elem, eventType, pars, aux) {
    var params = pars || {};
    var className = EVENT_MAP_INV[eventType];
    if (!className) {
      throw new Error('Event type ' + eventType + ' not supported.');
    }
    var evt = document.createEvent(className);
    switch (className) {
      case 'MouseEvents':
        {
          var clientX = params.x || params.clientX || 0;
          var clientY = params.y || params.clientY || 0;
          evt.initMouseEvent(eventType, params.bubbles || false, params.cancelable || true, window, params.clickCount || 1, 0,
          0,
          clientX,
          clientY,
          false, false, false, false, 0, null);
          break;
        }
      case 'KeyboardEvents':
        {
          var init = evt.initKeyboardEvent || evt.initKeyEvent;
          Common.defaults(params, {
            cancelable: true,
            ctrlKey: false,
            altKey: false,
            shiftKey: false,
            metaKey: false,
            keyCode: undefined,
            charCode: undefined
          });
          init(eventType, params.bubbles || false, params.cancelable, window, params.ctrlKey, params.altKey, params.shiftKey, params.metaKey, params.keyCode, params.charCode);
          break;
        }
      default:
        {
          evt.initEvent(eventType, params.bubbles || false, params.cancelable || true);
          break;
        }
    }
    Common.defaults(evt, aux);
    elem.dispatchEvent(evt);
  },
  bind: function bind(elem, event, func, newBool) {
    var bool = newBool || false;
    if (elem.addEventListener) {
      elem.addEventListener(event, func, bool);
    } else if (elem.attachEvent) {
      elem.attachEvent('on' + event, func);
    }
    return dom;
  },
  unbind: function unbind(elem, event, func, newBool) {
    var bool = newBool || false;
    if (elem.removeEventListener) {
      elem.removeEventListener(event, func, bool);
    } else if (elem.detachEvent) {
      elem.detachEvent('on' + event, func);
    }
    return dom;
  },
  addClass: function addClass(elem, className) {
    if (elem.className === undefined) {
      elem.className = className;
    } else if (elem.className !== className) {
      var classes = elem.className.split(/ +/);
      if (classes.indexOf(className) === -1) {
        classes.push(className);
        elem.className = classes.join(' ').replace(/^\s+/, '').replace(/\s+$/, '');
      }
    }
    return dom;
  },
  removeClass: function removeClass(elem, className) {
    if (className) {
      if (elem.className === className) {
        elem.removeAttribute('class');
      } else {
        var classes = elem.className.split(/ +/);
        var index = classes.indexOf(className);
        if (index !== -1) {
          classes.splice(index, 1);
          elem.className = classes.join(' ');
        }
      }
    } else {
      elem.className = undefined;
    }
    return dom;
  },
  hasClass: function hasClass(elem, className) {
    return new RegExp('(?:^|\\s+)' + className + '(?:\\s+|$)').test(elem.className) || false;
  },
  getWidth: function getWidth(elem) {
    var style = getComputedStyle(elem);
    return cssValueToPixels(style['border-left-width']) + cssValueToPixels(style['border-right-width']) + cssValueToPixels(style['padding-left']) + cssValueToPixels(style['padding-right']) + cssValueToPixels(style.width);
  },
  getHeight: function getHeight(elem) {
    var style = getComputedStyle(elem);
    return cssValueToPixels(style['border-top-width']) + cssValueToPixels(style['border-bottom-width']) + cssValueToPixels(style['padding-top']) + cssValueToPixels(style['padding-bottom']) + cssValueToPixels(style.height);
  },
  getOffset: function getOffset(el) {
    var elem = el;
    var offset = { left: 0, top: 0 };
    if (elem.offsetParent) {
      do {
        offset.left += elem.offsetLeft;
        offset.top += elem.offsetTop;
        elem = elem.offsetParent;
      } while (elem);
    }
    return offset;
  },
  isActive: function isActive(elem) {
    return elem === document.activeElement && (elem.type || elem.href);
  }
};

var BooleanController = function (_Controller) {
  inherits(BooleanController, _Controller);
  function BooleanController(object, property) {
    classCallCheck(this, BooleanController);
    var _this2 = possibleConstructorReturn(this, (BooleanController.__proto__ || Object.getPrototypeOf(BooleanController)).call(this, object, property));
    var _this = _this2;
    _this2.__prev = _this2.getValue();
    _this2.__checkbox = document.createElement('input');
    _this2.__checkbox.setAttribute('type', 'checkbox');
    function onChange() {
      _this.setValue(!_this.__prev);
    }
    dom.bind(_this2.__checkbox, 'change', onChange, false);
    _this2.domElement.appendChild(_this2.__checkbox);
    _this2.updateDisplay();
    return _this2;
  }
  createClass(BooleanController, [{
    key: 'setValue',
    value: function setValue(v) {
      var toReturn = get(BooleanController.prototype.__proto__ || Object.getPrototypeOf(BooleanController.prototype), 'setValue', this).call(this, v);
      if (this.__onFinishChange) {
        this.__onFinishChange.call(this, this.getValue());
      }
      this.__prev = this.getValue();
      return toReturn;
    }
  }, {
    key: 'updateDisplay',
    value: function updateDisplay() {
      if (this.getValue() === true) {
        this.__checkbox.setAttribute('checked', 'checked');
        this.__checkbox.checked = true;
        this.__prev = true;
      } else {
        this.__checkbox.checked = false;
        this.__prev = false;
      }
      return get(BooleanController.prototype.__proto__ || Object.getPrototypeOf(BooleanController.prototype), 'updateDisplay', this).call(this);
    }
  }]);
  return BooleanController;
}(Controller);

var OptionController = function (_Controller) {
  inherits(OptionController, _Controller);
  function OptionController(object, property, opts) {
    classCallCheck(this, OptionController);
    var _this2 = possibleConstructorReturn(this, (OptionController.__proto__ || Object.getPrototypeOf(OptionController)).call(this, object, property));
    var options = opts;
    var _this = _this2;
    _this2.__select = document.createElement('select');
    if (Common.isArray(options)) {
      var map = {};
      Common.each(options, function (element) {
        map[element] = element;
      });
      options = map;
    }
    Common.each(options, function (value, key) {
      var opt = document.createElement('option');
      opt.innerHTML = key;
      opt.setAttribute('value', value);
      _this.__select.appendChild(opt);
    });
    _this2.updateDisplay();
    dom.bind(_this2.__select, 'change', function () {
      var desiredValue = this.options[this.selectedIndex].value;
      _this.setValue(desiredValue);
    });
    _this2.domElement.appendChild(_this2.__select);
    return _this2;
  }
  createClass(OptionController, [{
    key: 'setValue',
    value: function setValue(v) {
      var toReturn = get(OptionController.prototype.__proto__ || Object.getPrototypeOf(OptionController.prototype), 'setValue', this).call(this, v);
      if (this.__onFinishChange) {
        this.__onFinishChange.call(this, this.getValue());
      }
      return toReturn;
    }
  }, {
    key: 'updateDisplay',
    value: function updateDisplay() {
      if (dom.isActive(this.__select)) return this;
      this.__select.value = this.getValue();
      return get(OptionController.prototype.__proto__ || Object.getPrototypeOf(OptionController.prototype), 'updateDisplay', this).call(this);
    }
  }]);
  return OptionController;
}(Controller);

var StringController = function (_Controller) {
  inherits(StringController, _Controller);
  function StringController(object, property) {
    classCallCheck(this, StringController);
    var _this2 = possibleConstructorReturn(this, (StringController.__proto__ || Object.getPrototypeOf(StringController)).call(this, object, property));
    var _this = _this2;
    function onChange() {
      _this.setValue(_this.__input.value);
    }
    function onBlur() {
      if (_this.__onFinishChange) {
        _this.__onFinishChange.call(_this, _this.getValue());
      }
    }
    _this2.__input = document.createElement('input');
    _this2.__input.setAttribute('type', 'text');
    dom.bind(_this2.__input, 'keyup', onChange);
    dom.bind(_this2.__input, 'change', onChange);
    dom.bind(_this2.__input, 'blur', onBlur);
    dom.bind(_this2.__input, 'keydown', function (e) {
      if (e.keyCode === 13) {
        this.blur();
      }
    });
    _this2.updateDisplay();
    _this2.domElement.appendChild(_this2.__input);
    return _this2;
  }
  createClass(StringController, [{
    key: 'updateDisplay',
    value: function updateDisplay() {
      if (!dom.isActive(this.__input)) {
        this.__input.value = this.getValue();
      }
      return get(StringController.prototype.__proto__ || Object.getPrototypeOf(StringController.prototype), 'updateDisplay', this).call(this);
    }
  }]);
  return StringController;
}(Controller);

function numDecimals(x) {
  var _x = x.toString();
  if (_x.indexOf('.') > -1) {
    return _x.length - _x.indexOf('.') - 1;
  }
  return 0;
}
var NumberController = function (_Controller) {
  inherits(NumberController, _Controller);
  function NumberController(object, property, params) {
    classCallCheck(this, NumberController);
    var _this = possibleConstructorReturn(this, (NumberController.__proto__ || Object.getPrototypeOf(NumberController)).call(this, object, property));
    var _params = params || {};
    _this.__min = _params.min;
    _this.__max = _params.max;
    _this.__step = _params.step;
    if (Common.isUndefined(_this.__step)) {
      if (_this.initialValue === 0) {
        _this.__impliedStep = 1;
      } else {
        _this.__impliedStep = Math.pow(10, Math.floor(Math.log(Math.abs(_this.initialValue)) / Math.LN10)) / 10;
      }
    } else {
      _this.__impliedStep = _this.__step;
    }
    _this.__precision = numDecimals(_this.__impliedStep);
    return _this;
  }
  createClass(NumberController, [{
    key: 'setValue',
    value: function setValue(v) {
      var _v = v;
      if (this.__min !== undefined && _v < this.__min) {
        _v = this.__min;
      } else if (this.__max !== undefined && _v > this.__max) {
        _v = this.__max;
      }
      if (this.__step !== undefined && _v % this.__step !== 0) {
        _v = Math.round(_v / this.__step) * this.__step;
      }
      return get(NumberController.prototype.__proto__ || Object.getPrototypeOf(NumberController.prototype), 'setValue', this).call(this, _v);
    }
  }, {
    key: 'min',
    value: function min(minValue) {
      this.__min = minValue;
      return this;
    }
  }, {
    key: 'max',
    value: function max(maxValue) {
      this.__max = maxValue;
      return this;
    }
  }, {
    key: 'step',
    value: function step(stepValue) {
      this.__step = stepValue;
      this.__impliedStep = stepValue;
      this.__precision = numDecimals(stepValue);
      return this;
    }
  }]);
  return NumberController;
}(Controller);

function roundToDecimal(value, decimals) {
  var tenTo = Math.pow(10, decimals);
  return Math.round(value * tenTo) / tenTo;
}
var NumberControllerBox = function (_NumberController) {
  inherits(NumberControllerBox, _NumberController);
  function NumberControllerBox(object, property, params) {
    classCallCheck(this, NumberControllerBox);
    var _this2 = possibleConstructorReturn(this, (NumberControllerBox.__proto__ || Object.getPrototypeOf(NumberControllerBox)).call(this, object, property, params));
    _this2.__truncationSuspended = false;
    var _this = _this2;
    var prevY = void 0;
    function onChange() {
      var attempted = parseFloat(_this.__input.value);
      if (!Common.isNaN(attempted)) {
        _this.setValue(attempted);
      }
    }
    function onFinish() {
      if (_this.__onFinishChange) {
        _this.__onFinishChange.call(_this, _this.getValue());
      }
    }
    function onBlur() {
      onFinish();
    }
    function onMouseDrag(e) {
      var diff = prevY - e.clientY;
      _this.setValue(_this.getValue() + diff * _this.__impliedStep);
      prevY = e.clientY;
    }
    function onMouseUp() {
      dom.unbind(window, 'mousemove', onMouseDrag);
      dom.unbind(window, 'mouseup', onMouseUp);
      onFinish();
    }
    function onMouseDown(e) {
      dom.bind(window, 'mousemove', onMouseDrag);
      dom.bind(window, 'mouseup', onMouseUp);
      prevY = e.clientY;
    }
    _this2.__input = document.createElement('input');
    _this2.__input.setAttribute('type', 'text');
    dom.bind(_this2.__input, 'change', onChange);
    dom.bind(_this2.__input, 'blur', onBlur);
    dom.bind(_this2.__input, 'mousedown', onMouseDown);
    dom.bind(_this2.__input, 'keydown', function (e) {
      if (e.keyCode === 13) {
        _this.__truncationSuspended = true;
        this.blur();
        _this.__truncationSuspended = false;
        onFinish();
      }
    });
    _this2.updateDisplay();
    _this2.domElement.appendChild(_this2.__input);
    return _this2;
  }
  createClass(NumberControllerBox, [{
    key: 'updateDisplay',
    value: function updateDisplay() {
      this.__input.value = this.__truncationSuspended ? this.getValue() : roundToDecimal(this.getValue(), this.__precision);
      return get(NumberControllerBox.prototype.__proto__ || Object.getPrototypeOf(NumberControllerBox.prototype), 'updateDisplay', this).call(this);
    }
  }]);
  return NumberControllerBox;
}(NumberController);

function map(v, i1, i2, o1, o2) {
  return o1 + (o2 - o1) * ((v - i1) / (i2 - i1));
}
var NumberControllerSlider = function (_NumberController) {
  inherits(NumberControllerSlider, _NumberController);
  function NumberControllerSlider(object, property, min, max, step) {
    classCallCheck(this, NumberControllerSlider);
    var _this2 = possibleConstructorReturn(this, (NumberControllerSlider.__proto__ || Object.getPrototypeOf(NumberControllerSlider)).call(this, object, property, { min: min, max: max, step: step }));
    var _this = _this2;
    _this2.__background = document.createElement('div');
    _this2.__foreground = document.createElement('div');
    dom.bind(_this2.__background, 'mousedown', onMouseDown);
    dom.bind(_this2.__background, 'touchstart', onTouchStart);
    dom.addClass(_this2.__background, 'slider');
    dom.addClass(_this2.__foreground, 'slider-fg');
    function onMouseDown(e) {
      document.activeElement.blur();
      dom.bind(window, 'mousemove', onMouseDrag);
      dom.bind(window, 'mouseup', onMouseUp);
      onMouseDrag(e);
    }
    function onMouseDrag(e) {
      e.preventDefault();
      var bgRect = _this.__background.getBoundingClientRect();
      _this.setValue(map(e.clientX, bgRect.left, bgRect.right, _this.__min, _this.__max));
      return false;
    }
    function onMouseUp() {
      dom.unbind(window, 'mousemove', onMouseDrag);
      dom.unbind(window, 'mouseup', onMouseUp);
      if (_this.__onFinishChange) {
        _this.__onFinishChange.call(_this, _this.getValue());
      }
    }
    function onTouchStart(e) {
      if (e.touches.length !== 1) {
        return;
      }
      dom.bind(window, 'touchmove', onTouchMove);
      dom.bind(window, 'touchend', onTouchEnd);
      onTouchMove(e);
    }
    function onTouchMove(e) {
      var clientX = e.touches[0].clientX;
      var bgRect = _this.__background.getBoundingClientRect();
      _this.setValue(map(clientX, bgRect.left, bgRect.right, _this.__min, _this.__max));
    }
    function onTouchEnd() {
      dom.unbind(window, 'touchmove', onTouchMove);
      dom.unbind(window, 'touchend', onTouchEnd);
      if (_this.__onFinishChange) {
        _this.__onFinishChange.call(_this, _this.getValue());
      }
    }
    _this2.updateDisplay();
    _this2.__background.appendChild(_this2.__foreground);
    _this2.domElement.appendChild(_this2.__background);
    return _this2;
  }
  createClass(NumberControllerSlider, [{
    key: 'updateDisplay',
    value: function updateDisplay() {
      var pct = (this.getValue() - this.__min) / (this.__max - this.__min);
      this.__foreground.style.width = pct * 100 + '%';
      return get(NumberControllerSlider.prototype.__proto__ || Object.getPrototypeOf(NumberControllerSlider.prototype), 'updateDisplay', this).call(this);
    }
  }]);
  return NumberControllerSlider;
}(NumberController);

var FunctionController = function (_Controller) {
  inherits(FunctionController, _Controller);
  function FunctionController(object, property, text) {
    classCallCheck(this, FunctionController);
    var _this2 = possibleConstructorReturn(this, (FunctionController.__proto__ || Object.getPrototypeOf(FunctionController)).call(this, object, property));
    var _this = _this2;
    _this2.__button = document.createElement('div');
    _this2.__button.innerHTML = text === undefined ? 'Fire' : text;
    dom.bind(_this2.__button, 'click', function (e) {
      e.preventDefault();
      _this.fire();
      return false;
    });
    dom.addClass(_this2.__button, 'button');
    _this2.domElement.appendChild(_this2.__button);
    return _this2;
  }
  createClass(FunctionController, [{
    key: 'fire',
    value: function fire() {
      if (this.__onChange) {
        this.__onChange.call(this);
      }
      this.getValue().call(this.object);
      if (this.__onFinishChange) {
        this.__onFinishChange.call(this, this.getValue());
      }
    }
  }]);
  return FunctionController;
}(Controller);

var ColorController = function (_Controller) {
  inherits(ColorController, _Controller);
  function ColorController(object, property) {
    classCallCheck(this, ColorController);
    var _this2 = possibleConstructorReturn(this, (ColorController.__proto__ || Object.getPrototypeOf(ColorController)).call(this, object, property));
    _this2.__color = new Color(_this2.getValue());
    _this2.__temp = new Color(0);
    var _this = _this2;
    _this2.domElement = document.createElement('div');
    dom.makeSelectable(_this2.domElement, false);
    _this2.__selector = document.createElement('div');
    _this2.__selector.className = 'selector';
    _this2.__saturation_field = document.createElement('div');
    _this2.__saturation_field.className = 'saturation-field';
    _this2.__field_knob = document.createElement('div');
    _this2.__field_knob.className = 'field-knob';
    _this2.__field_knob_border = '2px solid ';
    _this2.__hue_knob = document.createElement('div');
    _this2.__hue_knob.className = 'hue-knob';
    _this2.__hue_field = document.createElement('div');
    _this2.__hue_field.className = 'hue-field';
    _this2.__input = document.createElement('input');
    _this2.__input.type = 'text';
    _this2.__input_textShadow = '0 1px 1px ';
    dom.bind(_this2.__input, 'keydown', function (e) {
      if (e.keyCode === 13) {
        onBlur.call(this);
      }
    });
    dom.bind(_this2.__input, 'blur', onBlur);
    dom.bind(_this2.__selector, 'mousedown', function ()        {
      dom.addClass(this, 'drag').bind(window, 'mouseup', function ()        {
        dom.removeClass(_this.__selector, 'drag');
      });
    });
    dom.bind(_this2.__selector, 'touchstart', function ()        {
      dom.addClass(this, 'drag').bind(window, 'touchend', function ()        {
        dom.removeClass(_this.__selector, 'drag');
      });
    });
    var valueField = document.createElement('div');
    Common.extend(_this2.__selector.style, {
      width: '122px',
      height: '102px',
      padding: '3px',
      backgroundColor: '#222',
      boxShadow: '0px 1px 3px rgba(0,0,0,0.3)'
    });
    Common.extend(_this2.__field_knob.style, {
      position: 'absolute',
      width: '12px',
      height: '12px',
      border: _this2.__field_knob_border + (_this2.__color.v < 0.5 ? '#fff' : '#000'),
      boxShadow: '0px 1px 3px rgba(0,0,0,0.5)',
      borderRadius: '12px',
      zIndex: 1
    });
    Common.extend(_this2.__hue_knob.style, {
      position: 'absolute',
      width: '15px',
      height: '2px',
      borderRight: '4px solid #fff',
      zIndex: 1
    });
    Common.extend(_this2.__saturation_field.style, {
      width: '100px',
      height: '100px',
      border: '1px solid #555',
      marginRight: '3px',
      display: 'inline-block',
      cursor: 'pointer'
    });
    Common.extend(valueField.style, {
      width: '100%',
      height: '100%',
      background: 'none'
    });
    linearGradient(valueField, 'top', 'rgba(0,0,0,0)', '#000');
    Common.extend(_this2.__hue_field.style, {
      width: '15px',
      height: '100px',
      border: '1px solid #555',
      cursor: 'ns-resize',
      position: 'absolute',
      top: '3px',
      right: '3px'
    });
    hueGradient(_this2.__hue_field);
    Common.extend(_this2.__input.style, {
      outline: 'none',
      textAlign: 'center',
      color: '#fff',
      border: 0,
      fontWeight: 'bold',
      textShadow: _this2.__input_textShadow + 'rgba(0,0,0,0.7)'
    });
    dom.bind(_this2.__saturation_field, 'mousedown', fieldDown);
    dom.bind(_this2.__saturation_field, 'touchstart', fieldDown);
    dom.bind(_this2.__field_knob, 'mousedown', fieldDown);
    dom.bind(_this2.__field_knob, 'touchstart', fieldDown);
    dom.bind(_this2.__hue_field, 'mousedown', fieldDownH);
    dom.bind(_this2.__hue_field, 'touchstart', fieldDownH);
    function fieldDown(e) {
      setSV(e);
      dom.bind(window, 'mousemove', setSV);
      dom.bind(window, 'touchmove', setSV);
      dom.bind(window, 'mouseup', fieldUpSV);
      dom.bind(window, 'touchend', fieldUpSV);
    }
    function fieldDownH(e) {
      setH(e);
      dom.bind(window, 'mousemove', setH);
      dom.bind(window, 'touchmove', setH);
      dom.bind(window, 'mouseup', fieldUpH);
      dom.bind(window, 'touchend', fieldUpH);
    }
    function fieldUpSV() {
      dom.unbind(window, 'mousemove', setSV);
      dom.unbind(window, 'touchmove', setSV);
      dom.unbind(window, 'mouseup', fieldUpSV);
      dom.unbind(window, 'touchend', fieldUpSV);
      onFinish();
    }
    function fieldUpH() {
      dom.unbind(window, 'mousemove', setH);
      dom.unbind(window, 'touchmove', setH);
      dom.unbind(window, 'mouseup', fieldUpH);
      dom.unbind(window, 'touchend', fieldUpH);
      onFinish();
    }
    function onBlur() {
      var i = interpret(this.value);
      if (i !== false) {
        _this.__color.__state = i;
        _this.setValue(_this.__color.toOriginal());
      } else {
        this.value = _this.__color.toString();
      }
    }
    function onFinish() {
      if (_this.__onFinishChange) {
        _this.__onFinishChange.call(_this, _this.__color.toOriginal());
      }
    }
    _this2.__saturation_field.appendChild(valueField);
    _this2.__selector.appendChild(_this2.__field_knob);
    _this2.__selector.appendChild(_this2.__saturation_field);
    _this2.__selector.appendChild(_this2.__hue_field);
    _this2.__hue_field.appendChild(_this2.__hue_knob);
    _this2.domElement.appendChild(_this2.__input);
    _this2.domElement.appendChild(_this2.__selector);
    _this2.updateDisplay();
    function setSV(e) {
      if (e.type.indexOf('touch') === -1) {
        e.preventDefault();
      }
      var fieldRect = _this.__saturation_field.getBoundingClientRect();
      var _ref = e.touches && e.touches[0] || e,
          clientX = _ref.clientX,
          clientY = _ref.clientY;
      var s = (clientX - fieldRect.left) / (fieldRect.right - fieldRect.left);
      var v = 1 - (clientY - fieldRect.top) / (fieldRect.bottom - fieldRect.top);
      if (v > 1) {
        v = 1;
      } else if (v < 0) {
        v = 0;
      }
      if (s > 1) {
        s = 1;
      } else if (s < 0) {
        s = 0;
      }
      _this.__color.v = v;
      _this.__color.s = s;
      _this.setValue(_this.__color.toOriginal());
      return false;
    }
    function setH(e) {
      if (e.type.indexOf('touch') === -1) {
        e.preventDefault();
      }
      var fieldRect = _this.__hue_field.getBoundingClientRect();
      var _ref2 = e.touches && e.touches[0] || e,
          clientY = _ref2.clientY;
      var h = 1 - (clientY - fieldRect.top) / (fieldRect.bottom - fieldRect.top);
      if (h > 1) {
        h = 1;
      } else if (h < 0) {
        h = 0;
      }
      _this.__color.h = h * 360;
      _this.setValue(_this.__color.toOriginal());
      return false;
    }
    return _this2;
  }
  createClass(ColorController, [{
    key: 'updateDisplay',
    value: function updateDisplay() {
      var i = interpret(this.getValue());
      if (i !== false) {
        var mismatch = false;
        Common.each(Color.COMPONENTS, function (component) {
          if (!Common.isUndefined(i[component]) && !Common.isUndefined(this.__color.__state[component]) && i[component] !== this.__color.__state[component]) {
            mismatch = true;
            return {};
          }
        }, this);
        if (mismatch) {
          Common.extend(this.__color.__state, i);
        }
      }
      Common.extend(this.__temp.__state, this.__color.__state);
      this.__temp.a = 1;
      var flip = this.__color.v < 0.5 || this.__color.s > 0.5 ? 255 : 0;
      var _flip = 255 - flip;
      Common.extend(this.__field_knob.style, {
        marginLeft: 100 * this.__color.s - 7 + 'px',
        marginTop: 100 * (1 - this.__color.v) - 7 + 'px',
        backgroundColor: this.__temp.toHexString(),
        border: this.__field_knob_border + 'rgb(' + flip + ',' + flip + ',' + flip + ')'
      });
      this.__hue_knob.style.marginTop = (1 - this.__color.h / 360) * 100 + 'px';
      this.__temp.s = 1;
      this.__temp.v = 1;
      linearGradient(this.__saturation_field, 'left', '#fff', this.__temp.toHexString());
      this.__input.value = this.__color.toString();
      Common.extend(this.__input.style, {
        backgroundColor: this.__color.toHexString(),
        color: 'rgb(' + flip + ',' + flip + ',' + flip + ')',
        textShadow: this.__input_textShadow + 'rgba(' + _flip + ',' + _flip + ',' + _flip + ',.7)'
      });
    }
  }]);
  return ColorController;
}(Controller);
var vendors = ['-moz-', '-o-', '-webkit-', '-ms-', ''];
function linearGradient(elem, x, a, b) {
  elem.style.background = '';
  Common.each(vendors, function (vendor) {
    elem.style.cssText += 'background: ' + vendor + 'linear-gradient(' + x + ', ' + a + ' 0%, ' + b + ' 100%); ';
  });
}
function hueGradient(elem) {
  elem.style.background = '';
  elem.style.cssText += 'background: -moz-linear-gradient(top,  #ff0000 0%, #ff00ff 17%, #0000ff 34%, #00ffff 50%, #00ff00 67%, #ffff00 84%, #ff0000 100%);';
  elem.style.cssText += 'background: -webkit-linear-gradient(top,  #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);';
  elem.style.cssText += 'background: -o-linear-gradient(top,  #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);';
  elem.style.cssText += 'background: -ms-linear-gradient(top,  #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);';
  elem.style.cssText += 'background: linear-gradient(top,  #ff0000 0%,#ff00ff 17%,#0000ff 34%,#00ffff 50%,#00ff00 67%,#ffff00 84%,#ff0000 100%);';
}

var css = {
  load: function load(url, indoc) {
    var doc = indoc || document;
    var link = doc.createElement('link');
    link.type = 'text/css';
    link.rel = 'stylesheet';
    link.href = url;
    doc.getElementsByTagName('head')[0].appendChild(link);
  },
  inject: function inject(cssContent, indoc) {
    var doc = indoc || document;
    var injected = document.createElement('style');
    injected.type = 'text/css';
    injected.innerHTML = cssContent;
    var head = doc.getElementsByTagName('head')[0];
    try {
      head.appendChild(injected);
    } catch (e) {
    }
  }
};

var saveDialogContents = "<div id=\"dg-save\" class=\"dg dialogue\">\n\n  Here's the new load parameter for your <code>GUI</code>'s constructor:\n\n  <textarea id=\"dg-new-constructor\"></textarea>\n\n  <div id=\"dg-save-locally\">\n\n    <input id=\"dg-local-storage\" type=\"checkbox\"/> Automatically save\n    values to <code>localStorage</code> on exit.\n\n    <div id=\"dg-local-explain\">The values saved to <code>localStorage</code> will\n      override those passed to <code>dat.GUI</code>'s constructor. This makes it\n      easier to work incrementally, but <code>localStorage</code> is fragile,\n      and your friends may not see the same values you do.\n\n    </div>\n\n  </div>\n\n</div>";

var ControllerFactory = function ControllerFactory(object, property) {
  var initialValue = object[property];
  if (Common.isArray(arguments[2]) || Common.isObject(arguments[2])) {
    return new OptionController(object, property, arguments[2]);
  }
  if (Common.isNumber(initialValue)) {
    if (Common.isNumber(arguments[2]) && Common.isNumber(arguments[3])) {
      if (Common.isNumber(arguments[4])) {
        return new NumberControllerSlider(object, property, arguments[2], arguments[3], arguments[4]);
      }
      return new NumberControllerSlider(object, property, arguments[2], arguments[3]);
    }
    if (Common.isNumber(arguments[4])) {
      return new NumberControllerBox(object, property, { min: arguments[2], max: arguments[3], step: arguments[4] });
    }
    return new NumberControllerBox(object, property, { min: arguments[2], max: arguments[3] });
  }
  if (Common.isString(initialValue)) {
    return new StringController(object, property);
  }
  if (Common.isFunction(initialValue)) {
    return new FunctionController(object, property, '');
  }
  if (Common.isBoolean(initialValue)) {
    return new BooleanController(object, property);
  }
  return null;
};

function requestAnimationFrame$1(callback) {
  setTimeout(callback, 1000 / 60);
}
var requestAnimationFrame$1$1 = window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || requestAnimationFrame$1;

var CenteredDiv = function () {
  function CenteredDiv() {
    classCallCheck(this, CenteredDiv);
    this.backgroundElement = document.createElement('div');
    Common.extend(this.backgroundElement.style, {
      backgroundColor: 'rgba(0,0,0,0.8)',
      top: 0,
      left: 0,
      display: 'none',
      zIndex: '1000',
      opacity: 0,
      WebkitTransition: 'opacity 0.2s linear',
      transition: 'opacity 0.2s linear'
    });
    dom.makeFullscreen(this.backgroundElement);
    this.backgroundElement.style.position = 'fixed';
    this.domElement = document.createElement('div');
    Common.extend(this.domElement.style, {
      position: 'fixed',
      display: 'none',
      zIndex: '1001',
      opacity: 0,
      WebkitTransition: '-webkit-transform 0.2s ease-out, opacity 0.2s linear',
      transition: 'transform 0.2s ease-out, opacity 0.2s linear'
    });
    document.body.appendChild(this.backgroundElement);
    document.body.appendChild(this.domElement);
    var _this = this;
    dom.bind(this.backgroundElement, 'click', function () {
      _this.hide();
    });
  }
  createClass(CenteredDiv, [{
    key: 'show',
    value: function show() {
      var _this = this;
      this.backgroundElement.style.display = 'block';
      this.domElement.style.display = 'block';
      this.domElement.style.opacity = 0;
      this.domElement.style.webkitTransform = 'scale(1.1)';
      this.layout();
      Common.defer(function () {
        _this.backgroundElement.style.opacity = 1;
        _this.domElement.style.opacity = 1;
        _this.domElement.style.webkitTransform = 'scale(1)';
      });
    }
  }, {
    key: 'hide',
    value: function hide() {
      var _this = this;
      var hide = function hide() {
        _this.domElement.style.display = 'none';
        _this.backgroundElement.style.display = 'none';
        dom.unbind(_this.domElement, 'webkitTransitionEnd', hide);
        dom.unbind(_this.domElement, 'transitionend', hide);
        dom.unbind(_this.domElement, 'oTransitionEnd', hide);
      };
      dom.bind(this.domElement, 'webkitTransitionEnd', hide);
      dom.bind(this.domElement, 'transitionend', hide);
      dom.bind(this.domElement, 'oTransitionEnd', hide);
      this.backgroundElement.style.opacity = 0;
      this.domElement.style.opacity = 0;
      this.domElement.style.webkitTransform = 'scale(1.1)';
    }
  }, {
    key: 'layout',
    value: function layout() {
      this.domElement.style.left = window.innerWidth / 2 - dom.getWidth(this.domElement) / 2 + 'px';
      this.domElement.style.top = window.innerHeight / 2 - dom.getHeight(this.domElement) / 2 + 'px';
    }
  }]);
  return CenteredDiv;
}();

var styleSheet = ___$insertStyle(".dg ul{list-style:none;margin:0;padding:0;width:100%;clear:both}.dg.ac{position:fixed;top:0;left:0;right:0;height:0;z-index:0}.dg:not(.ac) .main{overflow:hidden}.dg.main{-webkit-transition:opacity .1s linear;-o-transition:opacity .1s linear;-moz-transition:opacity .1s linear;transition:opacity .1s linear}.dg.main.taller-than-window{overflow-y:auto}.dg.main.taller-than-window .close-button{opacity:1;margin-top:-1px;border-top:1px solid #2c2c2c}.dg.main ul.closed .close-button{opacity:1 !important}.dg.main:hover .close-button,.dg.main .close-button.drag{opacity:1}.dg.main .close-button{-webkit-transition:opacity .1s linear;-o-transition:opacity .1s linear;-moz-transition:opacity .1s linear;transition:opacity .1s linear;border:0;line-height:19px;height:20px;cursor:pointer;text-align:center;background-color:#000}.dg.main .close-button.close-top{position:relative}.dg.main .close-button.close-bottom{position:absolute}.dg.main .close-button:hover{background-color:#111}.dg.a{float:right;margin-right:15px;overflow-y:visible}.dg.a.has-save>ul.close-top{margin-top:0}.dg.a.has-save>ul.close-bottom{margin-top:27px}.dg.a.has-save>ul.closed{margin-top:0}.dg.a .save-row{top:0;z-index:1002}.dg.a .save-row.close-top{position:relative}.dg.a .save-row.close-bottom{position:fixed}.dg li{-webkit-transition:height .1s ease-out;-o-transition:height .1s ease-out;-moz-transition:height .1s ease-out;transition:height .1s ease-out;-webkit-transition:overflow .1s linear;-o-transition:overflow .1s linear;-moz-transition:overflow .1s linear;transition:overflow .1s linear}.dg li:not(.folder){cursor:auto;height:27px;line-height:27px;padding:0 4px 0 5px}.dg li.folder{padding:0;border-left:4px solid rgba(0,0,0,0)}.dg li.title{cursor:pointer;margin-left:-4px}.dg .closed li:not(.title),.dg .closed ul li,.dg .closed ul li>*{height:0;overflow:hidden;border:0}.dg .cr{clear:both;padding-left:3px;height:27px;overflow:hidden}.dg .property-name{cursor:default;float:left;clear:left;width:40%;overflow:hidden;text-overflow:ellipsis}.dg .c{float:left;width:60%;position:relative}.dg .c input[type=text]{border:0;margin-top:4px;padding:3px;width:100%;float:right}.dg .has-slider input[type=text]{width:30%;margin-left:0}.dg .slider{float:left;width:66%;margin-left:-5px;margin-right:0;height:19px;margin-top:4px}.dg .slider-fg{height:100%}.dg .c input[type=checkbox]{margin-top:7px}.dg .c select{margin-top:5px}.dg .cr.function,.dg .cr.function .property-name,.dg .cr.function *,.dg .cr.boolean,.dg .cr.boolean *{cursor:pointer}.dg .cr.color{overflow:visible}.dg .selector{display:none;position:absolute;margin-left:-9px;margin-top:23px;z-index:10}.dg .c:hover .selector,.dg .selector.drag{display:block}.dg li.save-row{padding:0}.dg li.save-row .button{display:inline-block;padding:0px 6px}.dg.dialogue{background-color:#222;width:460px;padding:15px;font-size:13px;line-height:15px}#dg-new-constructor{padding:10px;color:#222;font-family:Monaco, monospace;font-size:10px;border:0;resize:none;box-shadow:inset 1px 1px 1px #888;word-wrap:break-word;margin:12px 0;display:block;width:440px;overflow-y:scroll;height:100px;position:relative}#dg-local-explain{display:none;font-size:11px;line-height:17px;border-radius:3px;background-color:#333;padding:8px;margin-top:10px}#dg-local-explain code{font-size:10px}#dat-gui-save-locally{display:none}.dg{color:#eee;font:11px 'Lucida Grande', sans-serif;text-shadow:0 -1px 0 #111}.dg.main::-webkit-scrollbar{width:5px;background:#1a1a1a}.dg.main::-webkit-scrollbar-corner{height:0;display:none}.dg.main::-webkit-scrollbar-thumb{border-radius:5px;background:#676767}.dg li:not(.folder){background:#1a1a1a;border-bottom:1px solid #2c2c2c}.dg li.save-row{line-height:25px;background:#dad5cb;border:0}.dg li.save-row select{margin-left:5px;width:108px}.dg li.save-row .button{margin-left:5px;margin-top:1px;border-radius:2px;font-size:9px;line-height:7px;padding:4px 4px 5px 4px;background:#c5bdad;color:#fff;text-shadow:0 1px 0 #b0a58f;box-shadow:0 -1px 0 #b0a58f;cursor:pointer}.dg li.save-row .button.gears{background:#c5bdad url() 2px 1px no-repeat;height:7px;width:8px}.dg li.save-row .button:hover{background-color:#bab19e;box-shadow:0 -1px 0 #b0a58f}.dg li.folder{border-bottom:0}.dg li.title{padding-left:16px;background:#000 url() 6px 10px no-repeat;cursor:pointer;border-bottom:1px solid rgba(255,255,255,0.2)}.dg .closed li.title{background-image:url()}.dg .cr.boolean{border-left:3px solid #806787}.dg .cr.color{border-left:3px solid}.dg .cr.function{border-left:3px solid #e61d5f}.dg .cr.number{border-left:3px solid #2FA1D6}.dg .cr.number input[type=text]{color:#2FA1D6}.dg .cr.string{border-left:3px solid #1ed36f}.dg .cr.string input[type=text]{color:#1ed36f}.dg .cr.function:hover,.dg .cr.boolean:hover{background:#111}.dg .c input[type=text]{background:#303030;outline:none}.dg .c input[type=text]:hover{background:#3c3c3c}.dg .c input[type=text]:focus{background:#494949;color:#fff}.dg .c .slider{background:#303030;cursor:ew-resize}.dg .c .slider-fg{background:#2FA1D6;max-width:100%}.dg .c .slider:hover{background:#3c3c3c}.dg .c .slider:hover .slider-fg{background:#44abda}\n");

css.inject(styleSheet);
var CSS_NAMESPACE = 'dg';
var HIDE_KEY_CODE = 72;
var CLOSE_BUTTON_HEIGHT = 20;
var DEFAULT_DEFAULT_PRESET_NAME = 'Default';
var SUPPORTS_LOCAL_STORAGE = function () {
  try {
    return !!window.localStorage;
  } catch (e) {
    return false;
  }
}();
var SAVE_DIALOGUE = void 0;
var autoPlaceVirgin = true;
var autoPlaceContainer = void 0;
var hide = false;
var hideableGuis = [];
var GUI = function GUI(pars) {
  var _this = this;
  var params = pars || {};
  this.domElement = document.createElement('div');
  this.__ul = document.createElement('ul');
  this.domElement.appendChild(this.__ul);
  dom.addClass(this.domElement, CSS_NAMESPACE);
  this.__folders = {};
  this.__controllers = [];
  this.__rememberedObjects = [];
  this.__rememberedObjectIndecesToControllers = [];
  this.__listening = [];
  params = Common.defaults(params, {
    closeOnTop: false,
    autoPlace: true,
    width: GUI.DEFAULT_WIDTH
  });
  params = Common.defaults(params, {
    resizable: params.autoPlace,
    hideable: params.autoPlace
  });
  if (!Common.isUndefined(params.load)) {
    if (params.preset) {
      params.load.preset = params.preset;
    }
  } else {
    params.load = { preset: DEFAULT_DEFAULT_PRESET_NAME };
  }
  if (Common.isUndefined(params.parent) && params.hideable) {
    hideableGuis.push(this);
  }
  params.resizable = Common.isUndefined(params.parent) && params.resizable;
  if (params.autoPlace && Common.isUndefined(params.scrollable)) {
    params.scrollable = true;
  }
  var useLocalStorage = SUPPORTS_LOCAL_STORAGE && localStorage.getItem(getLocalStorageHash(this, 'isLocal')) === 'true';
  var saveToLocalStorage = void 0;
  var titleRow = void 0;
  Object.defineProperties(this,
  {
    parent: {
      get: function get$$1() {
        return params.parent;
      }
    },
    scrollable: {
      get: function get$$1() {
        return params.scrollable;
      }
    },
    autoPlace: {
      get: function get$$1() {
        return params.autoPlace;
      }
    },
    closeOnTop: {
      get: function get$$1() {
        return params.closeOnTop;
      }
    },
    preset: {
      get: function get$$1() {
        if (_this.parent) {
          return _this.getRoot().preset;
        }
        return params.load.preset;
      },
      set: function set$$1(v) {
        if (_this.parent) {
          _this.getRoot().preset = v;
        } else {
          params.load.preset = v;
        }
        setPresetSelectIndex(this);
        _this.revert();
      }
    },
    width: {
      get: function get$$1() {
        return params.width;
      },
      set: function set$$1(v) {
        params.width = v;
        setWidth(_this, v);
      }
    },
    name: {
      get: function get$$1() {
        return params.name;
      },
      set: function set$$1(v) {
        params.name = v;
        if (titleRow) {
          titleRow.innerHTML = params.name;
        }
      }
    },
    closed: {
      get: function get$$1() {
        return params.closed;
      },
      set: function set$$1(v) {
        params.closed = v;
        if (params.closed) {
          dom.addClass(_this.__ul, GUI.CLASS_CLOSED);
        } else {
          dom.removeClass(_this.__ul, GUI.CLASS_CLOSED);
        }
        this.onResize();
        if (_this.__closeButton) {
          _this.__closeButton.innerHTML = v ? GUI.TEXT_OPEN : GUI.TEXT_CLOSED;
        }
      }
    },
    load: {
      get: function get$$1() {
        return params.load;
      }
    },
    useLocalStorage: {
      get: function get$$1() {
        return useLocalStorage;
      },
      set: function set$$1(bool) {
        if (SUPPORTS_LOCAL_STORAGE) {
          useLocalStorage = bool;
          if (bool) {
            dom.bind(window, 'unload', saveToLocalStorage);
          } else {
            dom.unbind(window, 'unload', saveToLocalStorage);
          }
          localStorage.setItem(getLocalStorageHash(_this, 'isLocal'), bool);
        }
      }
    }
  });
  if (Common.isUndefined(params.parent)) {
    this.closed = params.closed || false;
    dom.addClass(this.domElement, GUI.CLASS_MAIN);
    dom.makeSelectable(this.domElement, false);
    if (SUPPORTS_LOCAL_STORAGE) {
      if (useLocalStorage) {
        _this.useLocalStorage = true;
        var savedGui = localStorage.getItem(getLocalStorageHash(this, 'gui'));
        if (savedGui) {
          params.load = JSON.parse(savedGui);
        }
      }
    }
    this.__closeButton = document.createElement('div');
    this.__closeButton.innerHTML = GUI.TEXT_CLOSED;
    dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_BUTTON);
    if (params.closeOnTop) {
      dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_TOP);
      this.domElement.insertBefore(this.__closeButton, this.domElement.childNodes[0]);
    } else {
      dom.addClass(this.__closeButton, GUI.CLASS_CLOSE_BOTTOM);
      this.domElement.appendChild(this.__closeButton);
    }
    dom.bind(this.__closeButton, 'click', function () {
      _this.closed = !_this.closed;
    });
  } else {
    if (params.closed === undefined) {
      params.closed = true;
    }
    var titleRowName = document.createTextNode(params.name);
    dom.addClass(titleRowName, 'controller-name');
    titleRow = addRow(_this, titleRowName);
    var onClickTitle = function onClickTitle(e) {
      e.preventDefault();
      _this.closed = !_this.closed;
      return false;
    };
    dom.addClass(this.__ul, GUI.CLASS_CLOSED);
    dom.addClass(titleRow, 'title');
    dom.bind(titleRow, 'click', onClickTitle);
    if (!params.closed) {
      this.closed = false;
    }
  }
  if (params.autoPlace) {
    if (Common.isUndefined(params.parent)) {
      if (autoPlaceVirgin) {
        autoPlaceContainer = document.createElement('div');
        dom.addClass(autoPlaceContainer, CSS_NAMESPACE);
        dom.addClass(autoPlaceContainer, GUI.CLASS_AUTO_PLACE_CONTAINER);
        document.body.appendChild(autoPlaceContainer);
        autoPlaceVirgin = false;
      }
      autoPlaceContainer.appendChild(this.domElement);
      dom.addClass(this.domElement, GUI.CLASS_AUTO_PLACE);
    }
    if (!this.parent) {
      setWidth(_this, params.width);
    }
  }
  this.__resizeHandler = function () {
    _this.onResizeDebounced();
  };
  dom.bind(window, 'resize', this.__resizeHandler);
  dom.bind(this.__ul, 'webkitTransitionEnd', this.__resizeHandler);
  dom.bind(this.__ul, 'transitionend', this.__resizeHandler);
  dom.bind(this.__ul, 'oTransitionEnd', this.__resizeHandler);
  this.onResize();
  if (params.resizable) {
    addResizeHandle(this);
  }
  saveToLocalStorage = function saveToLocalStorage() {
    if (SUPPORTS_LOCAL_STORAGE && localStorage.getItem(getLocalStorageHash(_this, 'isLocal')) === 'true') {
      localStorage.setItem(getLocalStorageHash(_this, 'gui'), JSON.stringify(_this.getSaveObject()));
    }
  };
  this.saveToLocalStorageIfPossible = saveToLocalStorage;
  function resetWidth() {
    var root = _this.getRoot();
    root.width += 1;
    Common.defer(function () {
      root.width -= 1;
    });
  }
  if (!params.parent) {
    resetWidth();
  }
};
GUI.toggleHide = function () {
  hide = !hide;
  Common.each(hideableGuis, function (gui) {
    gui.domElement.style.display = hide ? 'none' : '';
  });
};
GUI.CLASS_AUTO_PLACE = 'a';
GUI.CLASS_AUTO_PLACE_CONTAINER = 'ac';
GUI.CLASS_MAIN = 'main';
GUI.CLASS_CONTROLLER_ROW = 'cr';
GUI.CLASS_TOO_TALL = 'taller-than-window';
GUI.CLASS_CLOSED = 'closed';
GUI.CLASS_CLOSE_BUTTON = 'close-button';
GUI.CLASS_CLOSE_TOP = 'close-top';
GUI.CLASS_CLOSE_BOTTOM = 'close-bottom';
GUI.CLASS_DRAG = 'drag';
GUI.DEFAULT_WIDTH = 245;
GUI.TEXT_CLOSED = 'Close Controls';
GUI.TEXT_OPEN = 'Open Controls';
GUI._keydownHandler = function (e) {
  if (document.activeElement.type !== 'text' && (e.which === HIDE_KEY_CODE || e.keyCode === HIDE_KEY_CODE)) {
    GUI.toggleHide();
  }
};
dom.bind(window, 'keydown', GUI._keydownHandler, false);
Common.extend(GUI.prototype,
{
  add: function add(object, property) {
    return _add(this, object, property, {
      factoryArgs: Array.prototype.slice.call(arguments, 2)
    });
  },
  addColor: function addColor(object, property) {
    return _add(this, object, property, {
      color: true
    });
  },
  remove: function remove(controller) {
    this.__ul.removeChild(controller.__li);
    this.__controllers.splice(this.__controllers.indexOf(controller), 1);
    var _this = this;
    Common.defer(function () {
      _this.onResize();
    });
  },
  destroy: function destroy() {
    if (this.parent) {
      throw new Error('Only the root GUI should be removed with .destroy(). ' + 'For subfolders, use gui.removeFolder(folder) instead.');
    }
    if (this.autoPlace) {
      autoPlaceContainer.removeChild(this.domElement);
    }
    var _this = this;
    Common.each(this.__folders, function (subfolder) {
      _this.removeFolder(subfolder);
    });
    dom.unbind(window, 'keydown', GUI._keydownHandler, false);
    removeListeners(this);
  },
  addFolder: function addFolder(name) {
    if (this.__folders[name] !== undefined) {
      throw new Error('You already have a folder in this GUI by the' + ' name "' + name + '"');
    }
    var newGuiParams = { name: name, parent: this };
    newGuiParams.autoPlace = this.autoPlace;
    if (this.load &&
    this.load.folders &&
    this.load.folders[name]) {
      newGuiParams.closed = this.load.folders[name].closed;
      newGuiParams.load = this.load.folders[name];
    }
    var gui = new GUI(newGuiParams);
    this.__folders[name] = gui;
    var li = addRow(this, gui.domElement);
    dom.addClass(li, 'folder');
    return gui;
  },
  removeFolder: function removeFolder(folder) {
    this.__ul.removeChild(folder.domElement.parentElement);
    delete this.__folders[folder.name];
    if (this.load &&
    this.load.folders &&
    this.load.folders[folder.name]) {
      delete this.load.folders[folder.name];
    }
    removeListeners(folder);
    var _this = this;
    Common.each(folder.__folders, function (subfolder) {
      folder.removeFolder(subfolder);
    });
    Common.defer(function () {
      _this.onResize();
    });
  },
  open: function open() {
    this.closed = false;
  },
  close: function close() {
    this.closed = true;
  },
  hide: function hide() {
    this.domElement.style.display = 'none';
  },
  show: function show() {
    this.domElement.style.display = '';
  },
  onResize: function onResize() {
    var root = this.getRoot();
    if (root.scrollable) {
      var top = dom.getOffset(root.__ul).top;
      var h = 0;
      Common.each(root.__ul.childNodes, function (node) {
        if (!(root.autoPlace && node === root.__save_row)) {
          h += dom.getHeight(node);
        }
      });
      if (window.innerHeight - top - CLOSE_BUTTON_HEIGHT < h) {
        dom.addClass(root.domElement, GUI.CLASS_TOO_TALL);
        root.__ul.style.height = window.innerHeight - top - CLOSE_BUTTON_HEIGHT + 'px';
      } else {
        dom.removeClass(root.domElement, GUI.CLASS_TOO_TALL);
        root.__ul.style.height = 'auto';
      }
    }
    if (root.__resize_handle) {
      Common.defer(function () {
        root.__resize_handle.style.height = root.__ul.offsetHeight + 'px';
      });
    }
    if (root.__closeButton) {
      root.__closeButton.style.width = root.width + 'px';
    }
  },
  onResizeDebounced: Common.debounce(function () {
    this.onResize();
  }, 50),
  remember: function remember() {
    if (Common.isUndefined(SAVE_DIALOGUE)) {
      SAVE_DIALOGUE = new CenteredDiv();
      SAVE_DIALOGUE.domElement.innerHTML = saveDialogContents;
    }
    if (this.parent) {
      throw new Error('You can only call remember on a top level GUI.');
    }
    var _this = this;
    Common.each(Array.prototype.slice.call(arguments), function (object) {
      if (_this.__rememberedObjects.length === 0) {
        addSaveMenu(_this);
      }
      if (_this.__rememberedObjects.indexOf(object) === -1) {
        _this.__rememberedObjects.push(object);
      }
    });
    if (this.autoPlace) {
      setWidth(this, this.width);
    }
  },
  getRoot: function getRoot() {
    var gui = this;
    while (gui.parent) {
      gui = gui.parent;
    }
    return gui;
  },
  getSaveObject: function getSaveObject() {
    var toReturn = this.load;
    toReturn.closed = this.closed;
    if (this.__rememberedObjects.length > 0) {
      toReturn.preset = this.preset;
      if (!toReturn.remembered) {
        toReturn.remembered = {};
      }
      toReturn.remembered[this.preset] = getCurrentPreset(this);
    }
    toReturn.folders = {};
    Common.each(this.__folders, function (element, key) {
      toReturn.folders[key] = element.getSaveObject();
    });
    return toReturn;
  },
  save: function save() {
    if (!this.load.remembered) {
      this.load.remembered = {};
    }
    this.load.remembered[this.preset] = getCurrentPreset(this);
    markPresetModified(this, false);
    this.saveToLocalStorageIfPossible();
  },
  saveAs: function saveAs(presetName) {
    if (!this.load.remembered) {
      this.load.remembered = {};
      this.load.remembered[DEFAULT_DEFAULT_PRESET_NAME] = getCurrentPreset(this, true);
    }
    this.load.remembered[presetName] = getCurrentPreset(this);
    this.preset = presetName;
    addPresetOption(this, presetName, true);
    this.saveToLocalStorageIfPossible();
  },
  revert: function revert(gui) {
    Common.each(this.__controllers, function (controller) {
      if (!this.getRoot().load.remembered) {
        controller.setValue(controller.initialValue);
      } else {
        recallSavedValue(gui || this.getRoot(), controller);
      }
      if (controller.__onFinishChange) {
        controller.__onFinishChange.call(controller, controller.getValue());
      }
    }, this);
    Common.each(this.__folders, function (folder) {
      folder.revert(folder);
    });
    if (!gui) {
      markPresetModified(this.getRoot(), false);
    }
  },
  listen: function listen(controller) {
    var init = this.__listening.length === 0;
    this.__listening.push(controller);
    if (init) {
      updateDisplays(this.__listening);
    }
  },
  updateDisplay: function updateDisplay() {
    Common.each(this.__controllers, function (controller) {
      controller.updateDisplay();
    });
    Common.each(this.__folders, function (folder) {
      folder.updateDisplay();
    });
  }
});
function addRow(gui, newDom, liBefore) {
  var li = document.createElement('li');
  if (newDom) {
    li.appendChild(newDom);
  }
  if (liBefore) {
    gui.__ul.insertBefore(li, liBefore);
  } else {
    gui.__ul.appendChild(li);
  }
  gui.onResize();
  return li;
}
function removeListeners(gui) {
  dom.unbind(window, 'resize', gui.__resizeHandler);
  if (gui.saveToLocalStorageIfPossible) {
    dom.unbind(window, 'unload', gui.saveToLocalStorageIfPossible);
  }
}
function markPresetModified(gui, modified) {
  var opt = gui.__preset_select[gui.__preset_select.selectedIndex];
  if (modified) {
    opt.innerHTML = opt.value + '*';
  } else {
    opt.innerHTML = opt.value;
  }
}
function augmentController(gui, li, controller) {
  controller.__li = li;
  controller.__gui = gui;
  Common.extend(controller,                                   {
    options: function options(_options) {
      if (arguments.length > 1) {
        var nextSibling = controller.__li.nextElementSibling;
        controller.remove();
        return _add(gui, controller.object, controller.property, {
          before: nextSibling,
          factoryArgs: [Common.toArray(arguments)]
        });
      }
      if (Common.isArray(_options) || Common.isObject(_options)) {
        var _nextSibling = controller.__li.nextElementSibling;
        controller.remove();
        return _add(gui, controller.object, controller.property, {
          before: _nextSibling,
          factoryArgs: [_options]
        });
      }
    },
    name: function name(_name) {
      controller.__li.firstElementChild.firstElementChild.innerHTML = _name;
      return controller;
    },
    listen: function listen() {
      controller.__gui.listen(controller);
      return controller;
    },
    remove: function remove() {
      controller.__gui.remove(controller);
      return controller;
    }
  });
  if (controller instanceof NumberControllerSlider) {
    var box = new NumberControllerBox(controller.object, controller.property, { min: controller.__min, max: controller.__max, step: controller.__step });
    Common.each(['updateDisplay', 'onChange', 'onFinishChange', 'step', 'min', 'max'], function (method) {
      var pc = controller[method];
      var pb = box[method];
      controller[method] = box[method] = function () {
        var args = Array.prototype.slice.call(arguments);
        pb.apply(box, args);
        return pc.apply(controller, args);
      };
    });
    dom.addClass(li, 'has-slider');
    controller.domElement.insertBefore(box.domElement, controller.domElement.firstElementChild);
  } else if (controller instanceof NumberControllerBox) {
    var r = function r(returned) {
      if (Common.isNumber(controller.__min) && Common.isNumber(controller.__max)) {
        var oldName = controller.__li.firstElementChild.firstElementChild.innerHTML;
        var wasListening = controller.__gui.__listening.indexOf(controller) > -1;
        controller.remove();
        var newController = _add(gui, controller.object, controller.property, {
          before: controller.__li.nextElementSibling,
          factoryArgs: [controller.__min, controller.__max, controller.__step]
        });
        newController.name(oldName);
        if (wasListening) newController.listen();
        return newController;
      }
      return returned;
    };
    controller.min = Common.compose(r, controller.min);
    controller.max = Common.compose(r, controller.max);
  } else if (controller instanceof BooleanController) {
    dom.bind(li, 'click', function () {
      dom.fakeEvent(controller.__checkbox, 'click');
    });
    dom.bind(controller.__checkbox, 'click', function (e) {
      e.stopPropagation();
    });
  } else if (controller instanceof FunctionController) {
    dom.bind(li, 'click', function () {
      dom.fakeEvent(controller.__button, 'click');
    });
    dom.bind(li, 'mouseover', function () {
      dom.addClass(controller.__button, 'hover');
    });
    dom.bind(li, 'mouseout', function () {
      dom.removeClass(controller.__button, 'hover');
    });
  } else if (controller instanceof ColorController) {
    dom.addClass(li, 'color');
    controller.updateDisplay = Common.compose(function (val) {
      li.style.borderLeftColor = controller.__color.toString();
      return val;
    }, controller.updateDisplay);
    controller.updateDisplay();
  }
  controller.setValue = Common.compose(function (val) {
    if (gui.getRoot().__preset_select && controller.isModified()) {
      markPresetModified(gui.getRoot(), true);
    }
    return val;
  }, controller.setValue);
}
function recallSavedValue(gui, controller) {
  var root = gui.getRoot();
  var matchedIndex = root.__rememberedObjects.indexOf(controller.object);
  if (matchedIndex !== -1) {
    var controllerMap = root.__rememberedObjectIndecesToControllers[matchedIndex];
    if (controllerMap === undefined) {
      controllerMap = {};
      root.__rememberedObjectIndecesToControllers[matchedIndex] = controllerMap;
    }
    controllerMap[controller.property] = controller;
    if (root.load && root.load.remembered) {
      var presetMap = root.load.remembered;
      var preset = void 0;
      if (presetMap[gui.preset]) {
        preset = presetMap[gui.preset];
      } else if (presetMap[DEFAULT_DEFAULT_PRESET_NAME]) {
        preset = presetMap[DEFAULT_DEFAULT_PRESET_NAME];
      } else {
        return;
      }
      if (preset[matchedIndex] && preset[matchedIndex][controller.property] !== undefined) {
        var value = preset[matchedIndex][controller.property];
        controller.initialValue = value;
        controller.setValue(value);
      }
    }
  }
}
function _add(gui, object, property, params) {
  if (object[property] === undefined) {
    throw new Error('Object "' + object + '" has no property "' + property + '"');
  }
  var controller = void 0;
  if (params.color) {
    controller = new ColorController(object, property);
  } else {
    var factoryArgs = [object, property].concat(params.factoryArgs);
    controller = ControllerFactory.apply(gui, factoryArgs);
  }
  if (params.before instanceof Controller) {
    params.before = params.before.__li;
  }
  recallSavedValue(gui, controller);
  dom.addClass(controller.domElement, 'c');
  var name = document.createElement('span');
  dom.addClass(name, 'property-name');
  name.innerHTML = controller.property;
  var container = document.createElement('div');
  container.appendChild(name);
  container.appendChild(controller.domElement);
  var li = addRow(gui, container, params.before);
  dom.addClass(li, GUI.CLASS_CONTROLLER_ROW);
  if (controller instanceof ColorController) {
    dom.addClass(li, 'color');
  } else {
    dom.addClass(li, _typeof(controller.getValue()));
  }
  augmentController(gui, li, controller);
  gui.__controllers.push(controller);
  return controller;
}
function getLocalStorageHash(gui, key) {
  return document.location.href + '.' + key;
}
function addPresetOption(gui, name, setSelected) {
  var opt = document.createElement('option');
  opt.innerHTML = name;
  opt.value = name;
  gui.__preset_select.appendChild(opt);
  if (setSelected) {
    gui.__preset_select.selectedIndex = gui.__preset_select.length - 1;
  }
}
function showHideExplain(gui, explain) {
  explain.style.display = gui.useLocalStorage ? 'block' : 'none';
}
function addSaveMenu(gui) {
  var div = gui.__save_row = document.createElement('li');
  dom.addClass(gui.domElement, 'has-save');
  gui.__ul.insertBefore(div, gui.__ul.firstChild);
  dom.addClass(div, 'save-row');
  var gears = document.createElement('span');
  gears.innerHTML = '&nbsp;';
  dom.addClass(gears, 'button gears');
  var button = document.createElement('span');
  button.innerHTML = 'Save';
  dom.addClass(button, 'button');
  dom.addClass(button, 'save');
  var button2 = document.createElement('span');
  button2.innerHTML = 'New';
  dom.addClass(button2, 'button');
  dom.addClass(button2, 'save-as');
  var button3 = document.createElement('span');
  button3.innerHTML = 'Revert';
  dom.addClass(button3, 'button');
  dom.addClass(button3, 'revert');
  var select = gui.__preset_select = document.createElement('select');
  if (gui.load && gui.load.remembered) {
    Common.each(gui.load.remembered, function (value, key) {
      addPresetOption(gui, key, key === gui.preset);
    });
  } else {
    addPresetOption(gui, DEFAULT_DEFAULT_PRESET_NAME, false);
  }
  dom.bind(select, 'change', function () {
    for (var index = 0; index < gui.__preset_select.length; index++) {
      gui.__preset_select[index].innerHTML = gui.__preset_select[index].value;
    }
    gui.preset = this.value;
  });
  div.appendChild(select);
  div.appendChild(gears);
  div.appendChild(button);
  div.appendChild(button2);
  div.appendChild(button3);
  if (SUPPORTS_LOCAL_STORAGE) {
    var explain = document.getElementById('dg-local-explain');
    var localStorageCheckBox = document.getElementById('dg-local-storage');
    var saveLocally = document.getElementById('dg-save-locally');
    saveLocally.style.display = 'block';
    if (localStorage.getItem(getLocalStorageHash(gui, 'isLocal')) === 'true') {
      localStorageCheckBox.setAttribute('checked', 'checked');
    }
    showHideExplain(gui, explain);
    dom.bind(localStorageCheckBox, 'change', function () {
      gui.useLocalStorage = !gui.useLocalStorage;
      showHideExplain(gui, explain);
    });
  }
  var newConstructorTextArea = document.getElementById('dg-new-constructor');
  dom.bind(newConstructorTextArea, 'keydown', function (e) {
    if (e.metaKey && (e.which === 67 || e.keyCode === 67)) {
      SAVE_DIALOGUE.hide();
    }
  });
  dom.bind(gears, 'click', function () {
    newConstructorTextArea.innerHTML = JSON.stringify(gui.getSaveObject(), undefined, 2);
    SAVE_DIALOGUE.show();
    newConstructorTextArea.focus();
    newConstructorTextArea.select();
  });
  dom.bind(button, 'click', function () {
    gui.save();
  });
  dom.bind(button2, 'click', function () {
    var presetName = prompt('Enter a new preset name.');
    if (presetName) {
      gui.saveAs(presetName);
    }
  });
  dom.bind(button3, 'click', function () {
    gui.revert();
  });
}
function addResizeHandle(gui) {
  var pmouseX = void 0;
  gui.__resize_handle = document.createElement('div');
  Common.extend(gui.__resize_handle.style, {
    width: '6px',
    marginLeft: '-3px',
    height: '200px',
    cursor: 'ew-resize',
    position: 'absolute'
  });
  function drag(e) {
    e.preventDefault();
    gui.width += pmouseX - e.clientX;
    gui.onResize();
    pmouseX = e.clientX;
    return false;
  }
  function dragStop() {
    dom.removeClass(gui.__closeButton, GUI.CLASS_DRAG);
    dom.unbind(window, 'mousemove', drag);
    dom.unbind(window, 'mouseup', dragStop);
  }
  function dragStart(e) {
    e.preventDefault();
    pmouseX = e.clientX;
    dom.addClass(gui.__closeButton, GUI.CLASS_DRAG);
    dom.bind(window, 'mousemove', drag);
    dom.bind(window, 'mouseup', dragStop);
    return false;
  }
  dom.bind(gui.__resize_handle, 'mousedown', dragStart);
  dom.bind(gui.__closeButton, 'mousedown', dragStart);
  gui.domElement.insertBefore(gui.__resize_handle, gui.domElement.firstElementChild);
}
function setWidth(gui, w) {
  gui.domElement.style.width = w + 'px';
  if (gui.__save_row && gui.autoPlace) {
    gui.__save_row.style.width = w + 'px';
  }
  if (gui.__closeButton) {
    gui.__closeButton.style.width = w + 'px';
  }
}
function getCurrentPreset(gui, useInitialValues) {
  var toReturn = {};
  Common.each(gui.__rememberedObjects, function (val, index) {
    var savedValues = {};
    var controllerMap = gui.__rememberedObjectIndecesToControllers[index];
    Common.each(controllerMap, function (controller, property) {
      savedValues[property] = useInitialValues ? controller.initialValue : controller.getValue();
    });
    toReturn[index] = savedValues;
  });
  return toReturn;
}
function setPresetSelectIndex(gui) {
  for (var index = 0; index < gui.__preset_select.length; index++) {
    if (gui.__preset_select[index].value === gui.preset) {
      gui.__preset_select.selectedIndex = index;
    }
  }
}
function updateDisplays(controllerArray) {
  if (controllerArray.length !== 0) {
    requestAnimationFrame$1$1.call(window, function () {
      updateDisplays(controllerArray);
    });
  }
  Common.each(controllerArray, function (c) {
    c.updateDisplay();
  });
}
var GUI$1 = GUI;
//# sourceMappingURL=dat.gui.module.js.map

/**
 * Copyright 2014 Google Inc. All rights reserved.
 *
 * Use of this source code is governed by a BSD-style
 * license that can be found in the LICENSE file.
 *
 * @fileoverview Description of this file.
 *
 * A polyfill for HTML Canvas features, including
 * Path2D support.
 */

(function (CanvasRenderingContext2D, nodeRequire) {

if (CanvasRenderingContext2D == undefined) {
  CanvasRenderingContext2D = nodeRequire('canvas').Context2d;
}

if (CanvasRenderingContext2D.prototype.ellipse == undefined) {
  CanvasRenderingContext2D.prototype.ellipse = function(x, y, radiusX, radiusY, rotation, startAngle, endAngle, antiClockwise) {
    this.save();
    this.translate(x, y);
    this.rotate(rotation);
    this.scale(radiusX, radiusY);
    this.arc(0, 0, 1, startAngle, endAngle, antiClockwise);
    this.restore();
  };
}

if (typeof Path2D !== 'function' || 
    typeof new Path2D().addPath !== 'function') {
  (function() {

    // Include the SVG path parser.
    var parser = (function() {
      /*
       * Generated by PEG.js 0.8.0.
       *
       * http://pegjs.majda.cz/
       */
    
      function peg$subclass(child, parent) {
        function ctor() { this.constructor = child; }
        ctor.prototype = parent.prototype;
        child.prototype = new ctor();
      }
    
      function SyntaxError(message, expected, found, offset, line, column) {
        this.message  = message;
        this.expected = expected;
        this.found    = found;
        this.offset   = offset;
        this.line     = line;
        this.column   = column;
    
        this.name     = "SyntaxError";
      }
    
      peg$subclass(SyntaxError, Error);
    
      function parse(input) {
        var options = arguments.length > 1 ? arguments[1] : {},
    
            peg$FAILED = {},
    
            peg$startRuleFunctions = { svg_path: peg$parsesvg_path },
            peg$startRuleFunction  = peg$parsesvg_path,
    
            peg$c0 = peg$FAILED,
            peg$c2 = null,
            peg$c3 = function(d) { return ops; },
            peg$c4 = /^[Mm]/,
            peg$c5 = { type: "class", value: "[Mm]", description: "[Mm]" },
            peg$c6 = function(ch, args) {
                  var moveCh = ch;
                  // If this is the first move cmd then force it to be absolute.
                  if (firstSubPath) {
                    moveCh = 'M';
                    firstSubPath = false;
                  }
                  ops.push({type: 'moveTo', args: makeAbsolute(moveCh, args[0])});
                  for (var i=1; i < args.length; i++) {
                    // The lineTo args are either abs or relative, depending on the
                    // original moveto command.
                    ops.push({type: 'lineTo', args: makeAbsolute(ch, args[i])});
                  }
                },
            peg$c7 = function(one, rest) { return concatSequence(one, rest); },
            peg$c8 = /^[Zz]/,
            peg$c9 = { type: "class", value: "[Zz]", description: "[Zz]" },
            peg$c10 = function() { ops.push({type: 'closePath', args: []}); },
            peg$c11 = /^[Ll]/,
            peg$c12 = { type: "class", value: "[Ll]", description: "[Ll]" },
            peg$c13 = function(ch, args) {
                  for (var i=0; i < args.length; i++) {
                    ops.push({type: 'lineTo', args: makeAbsolute(ch, args[i])});
                  }
                },
            peg$c14 = /^[Hh]/,
            peg$c15 = { type: "class", value: "[Hh]", description: "[Hh]" },
            peg$c16 = function(ch, args) {
                for (var i=0; i < args.length; i++) {
                  ops.push({type: 'lineTo', args: makeAbsoluteFromX(ch, args[i])});
                }
              },
            peg$c17 = /^[Vv]/,
            peg$c18 = { type: "class", value: "[Vv]", description: "[Vv]" },
            peg$c19 = function(ch, args) {
                for (var i=0; i < args.length; i++) {
                  ops.push({type: 'lineTo', args: makeAbsoluteFromY(ch, args[i])});
                }
              },
            peg$c20 = /^[Cc]/,
            peg$c21 = { type: "class", value: "[Cc]", description: "[Cc]" },
            peg$c22 = function(ch, args) {
                for (var i=0; i < args.length; i++) {
                  ops.push({type: 'bezierCurveTo', args: makeAbsoluteMultiple(ch, args[i])});
                }
              },
            peg$c23 = function(cp1, cp2, last) { return cp1.concat(cp2, last); },
            peg$c24 = /^[Ss]/,
            peg$c25 = { type: "class", value: "[Ss]", description: "[Ss]" },
            peg$c26 = function(ch, args) {
                for (var i=0; i < args.length; i++) {
                  ops.push({type: 'bezierCurveTo', args: makeReflected().concat(makeAbsoluteMultiple(ch, args[i]))});
                }
              },
            peg$c27 = function(cp1, last) { return cp1.concat(last); },
            peg$c28 = /^[Qq]/,
            peg$c29 = { type: "class", value: "[Qq]", description: "[Qq]" },
            peg$c30 = function(ch, args) {
                for (var i=0; i < args.length; i++) {
                  ops.push({type: 'quadraticCurveTo', args: makeAbsoluteMultiple(ch, args[i])});
                }
              },
            peg$c31 = /^[Tt]/,
            peg$c32 = { type: "class", value: "[Tt]", description: "[Tt]" },
            peg$c33 = function(ch, args) {
                for (var i=0; i < args.length; i++) {
                  var reflected = makeReflected();
                  ops.push({type: 'quadraticCurveTo', args: reflected.concat(makeAbsoluteMultiple(ch, args[i]))});
                  lastControl = reflected.slice(0);
                }
              },
            peg$c34 = /^[Aa]/,
            peg$c35 = { type: "class", value: "[Aa]", description: "[Aa]" },
            peg$c36 = function(ch, args) {
                for (var i=0; i < args.length; i++) {
                  var x1 = [lastCoord.slice()];
                  var x2 = [makeAbsolute(ch, args[i].slice(-2))];
                  absArgs = x1.concat(args[i].slice(0, -2), x2);
                  ellipseFromEllipticalArc.apply(this, absArgs);
                }
              },
            peg$c37 = function(rx, ry, xrot, large, sweep, last) { return [parseFloat(rx), parseFloat(ry), parseFloat(flatten(xrot).join('')), parseInt(large), parseInt(sweep), last[0], last[1]]; },
            peg$c38 = function(x, y) { return [x, y] },
            peg$c39 = function(number) { return parseFloat(flatten(number).join('')) },
            peg$c40 = "0",
            peg$c41 = { type: "literal", value: "0", description: "\"0\"" },
            peg$c42 = "1",
            peg$c43 = { type: "literal", value: "1", description: "\"1\"" },
            peg$c44 = ",",
            peg$c45 = { type: "literal", value: ",", description: "\",\"" },
            peg$c46 = ".",
            peg$c47 = { type: "literal", value: ".", description: "\".\"" },
            peg$c48 = /^[eE]/,
            peg$c49 = { type: "class", value: "[eE]", description: "[eE]" },
            peg$c50 = "+",
            peg$c51 = { type: "literal", value: "+", description: "\"+\"" },
            peg$c52 = "-",
            peg$c53 = { type: "literal", value: "-", description: "\"-\"" },
            peg$c54 = /^[0-9]/,
            peg$c55 = { type: "class", value: "[0-9]", description: "[0-9]" },
            peg$c56 = function(digits) { return digits.join('') },
            peg$c57 = /^[ \t\n\r]/,
            peg$c58 = { type: "class", value: "[ \\t\\n\\r]", description: "[ \\t\\n\\r]" },
    
            peg$currPos          = 0,
            peg$cachedPos        = 0,
            peg$cachedPosDetails = { line: 1, column: 1, seenCR: false },
            peg$maxFailPos       = 0,
            peg$maxFailExpected  = [],
            peg$result;
    
        if ("startRule" in options) {
          if (!(options.startRule in peg$startRuleFunctions)) {
            throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
          }
    
          peg$startRuleFunction = peg$startRuleFunctions[options.startRule];
        }
    
        function peg$computePosDetails(pos) {
          function advance(details, startPos, endPos) {
            var p, ch;
    
            for (p = startPos; p < endPos; p++) {
              ch = input.charAt(p);
              if (ch === "\n") {
                if (!details.seenCR) { details.line++; }
                details.column = 1;
                details.seenCR = false;
              } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") {
                details.line++;
                details.column = 1;
                details.seenCR = true;
              } else {
                details.column++;
                details.seenCR = false;
              }
            }
          }
    
          if (peg$cachedPos !== pos) {
            if (peg$cachedPos > pos) {
              peg$cachedPos = 0;
              peg$cachedPosDetails = { line: 1, column: 1, seenCR: false };
            }
            advance(peg$cachedPosDetails, peg$cachedPos, pos);
            peg$cachedPos = pos;
          }
    
          return peg$cachedPosDetails;
        }
    
        function peg$fail(expected) {
          if (peg$currPos < peg$maxFailPos) { return; }
    
          if (peg$currPos > peg$maxFailPos) {
            peg$maxFailPos = peg$currPos;
            peg$maxFailExpected = [];
          }
    
          peg$maxFailExpected.push(expected);
        }
    
        function peg$buildException(message, expected, pos) {
          function cleanupExpected(expected) {
            var i = 1;
    
            expected.sort(function(a, b) {
              if (a.description < b.description) {
                return -1;
              } else if (a.description > b.description) {
                return 1;
              } else {
                return 0;
              }
            });
    
            while (i < expected.length) {
              if (expected[i - 1] === expected[i]) {
                expected.splice(i, 1);
              } else {
                i++;
              }
            }
          }
    
          function buildMessage(expected, found) {
            function stringEscape(s) {
              function hex(ch) { return ch.charCodeAt(0).toString(16).toUpperCase(); }
    
              return s
                .replace(/\\/g,   '\\\\')
                .replace(/"/g,    '\\"')
                .replace(/\x08/g, '\\b')
                .replace(/\t/g,   '\\t')
                .replace(/\n/g,   '\\n')
                .replace(/\f/g,   '\\f')
                .replace(/\r/g,   '\\r')
                .replace(/[\x00-\x07\x0B\x0E\x0F]/g, function(ch) { return '\\x0' + hex(ch); })
                .replace(/[\x10-\x1F\x80-\xFF]/g,    function(ch) { return '\\x'  + hex(ch); })
                .replace(/[\u0180-\u0FFF]/g,         function(ch) { return '\\u0' + hex(ch); })
                .replace(/[\u1080-\uFFFF]/g,         function(ch) { return '\\u'  + hex(ch); });
            }
    
            var expectedDescs = new Array(expected.length),
                expectedDesc, foundDesc, i;
    
            for (i = 0; i < expected.length; i++) {
              expectedDescs[i] = expected[i].description;
            }
    
            expectedDesc = expected.length > 1
              ? expectedDescs.slice(0, -1).join(", ")
                  + " or "
                  + expectedDescs[expected.length - 1]
              : expectedDescs[0];
    
            foundDesc = found ? "\"" + stringEscape(found) + "\"" : "end of input";
    
            return "Expected " + expectedDesc + " but " + foundDesc + " found.";
          }
    
          var posDetails = peg$computePosDetails(pos),
              found      = pos < input.length ? input.charAt(pos) : null;
    
          if (expected !== null) {
            cleanupExpected(expected);
          }
    
          return new SyntaxError(
            message !== null ? message : buildMessage(expected, found),
            expected,
            found,
            pos,
            posDetails.line,
            posDetails.column
          );
        }
    
        function peg$parsesvg_path() {
          var s0, s1, s2, s3, s4;
    
          s0 = peg$currPos;
          s1 = [];
          s2 = peg$parsewsp();
          while (s2 !== peg$FAILED) {
            s1.push(s2);
            s2 = peg$parsewsp();
          }
          if (s1 !== peg$FAILED) {
            s2 = peg$parsemoveTo_drawTo_commandGroups();
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s3 = [];
              s4 = peg$parsewsp();
              while (s4 !== peg$FAILED) {
                s3.push(s4);
                s4 = peg$parsewsp();
              }
              if (s3 !== peg$FAILED) {
                s1 = peg$c3(s2);
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsemoveTo_drawTo_commandGroups() {
          var s0, s1, s2, s3, s4;
    
          s0 = peg$currPos;
          s1 = peg$parsemoveTo_drawTo_commandGroup();
          if (s1 !== peg$FAILED) {
            s2 = peg$currPos;
            s3 = [];
            s4 = peg$parsewsp();
            while (s4 !== peg$FAILED) {
              s3.push(s4);
              s4 = peg$parsewsp();
            }
            if (s3 !== peg$FAILED) {
              s4 = peg$parsemoveTo_drawTo_commandGroups();
              if (s4 !== peg$FAILED) {
                s3 = [s3, s4];
                s2 = s3;
              } else {
                peg$currPos = s2;
                s2 = peg$c0;
              }
            } else {
              peg$currPos = s2;
              s2 = peg$c0;
            }
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s1 = [s1, s2];
              s0 = s1;
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsemoveTo_drawTo_commandGroup() {
          var s0, s1, s2, s3, s4;
    
          s0 = peg$currPos;
          s1 = peg$parsemoveto();
          if (s1 !== peg$FAILED) {
            s2 = peg$currPos;
            s3 = [];
            s4 = peg$parsewsp();
            while (s4 !== peg$FAILED) {
              s3.push(s4);
              s4 = peg$parsewsp();
            }
            if (s3 !== peg$FAILED) {
              s4 = peg$parsedrawto_commands();
              if (s4 !== peg$FAILED) {
                s3 = [s3, s4];
                s2 = s3;
              } else {
                peg$currPos = s2;
                s2 = peg$c0;
              }
            } else {
              peg$currPos = s2;
              s2 = peg$c0;
            }
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s1 = [s1, s2];
              s0 = s1;
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsedrawto_commands() {
          var s0, s1, s2, s3, s4;
    
          s0 = peg$currPos;
          s1 = peg$parsedrawto_command();
          if (s1 !== peg$FAILED) {
            s2 = peg$currPos;
            s3 = [];
            s4 = peg$parsewsp();
            while (s4 !== peg$FAILED) {
              s3.push(s4);
              s4 = peg$parsewsp();
            }
            if (s3 !== peg$FAILED) {
              s4 = peg$parsedrawto_commands();
              if (s4 !== peg$FAILED) {
                s3 = [s3, s4];
                s2 = s3;
              } else {
                peg$currPos = s2;
                s2 = peg$c0;
              }
            } else {
              peg$currPos = s2;
              s2 = peg$c0;
            }
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s1 = [s1, s2];
              s0 = s1;
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsedrawto_command() {
          var s0;
    
          s0 = peg$parseclosepath();
          if (s0 === peg$FAILED) {
            s0 = peg$parselineto();
            if (s0 === peg$FAILED) {
              s0 = peg$parsehorizontal_lineto();
              if (s0 === peg$FAILED) {
                s0 = peg$parsevertical_lineto();
                if (s0 === peg$FAILED) {
                  s0 = peg$parsecurveto();
                  if (s0 === peg$FAILED) {
                    s0 = peg$parsesmooth_curveto();
                    if (s0 === peg$FAILED) {
                      s0 = peg$parsequadratic_bezier_curveto();
                      if (s0 === peg$FAILED) {
                        s0 = peg$parsesmooth_quadratic_bezier_curveto();
                        if (s0 === peg$FAILED) {
                          s0 = peg$parseelliptical_arc();
                        }
                      }
                    }
                  }
                }
              }
            }
          }
    
          return s0;
        }
    
        function peg$parsemoveto() {
          var s0, s1, s2, s3;
    
          s0 = peg$currPos;
          if (peg$c4.test(input.charAt(peg$currPos))) {
            s1 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s1 = peg$FAILED;
            { peg$fail(peg$c5); }
          }
          if (s1 !== peg$FAILED) {
            s2 = [];
            s3 = peg$parsewsp();
            while (s3 !== peg$FAILED) {
              s2.push(s3);
              s3 = peg$parsewsp();
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parsemoveto_argument_sequence();
              if (s3 !== peg$FAILED) {
                s1 = peg$c6(s1, s3);
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsemoveto_argument_sequence() {
          var s0, s1, s2, s3, s4;
    
          s0 = peg$currPos;
          s1 = peg$parsecoordinate_pair();
          if (s1 !== peg$FAILED) {
            s2 = peg$currPos;
            s3 = peg$parsecomma_wsp();
            if (s3 === peg$FAILED) {
              s3 = peg$c2;
            }
            if (s3 !== peg$FAILED) {
              s4 = peg$parselineto_argument_sequence();
              if (s4 !== peg$FAILED) {
                s3 = [s3, s4];
                s2 = s3;
              } else {
                peg$currPos = s2;
                s2 = peg$c0;
              }
            } else {
              peg$currPos = s2;
              s2 = peg$c0;
            }
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s1 = peg$c7(s1, s2);
              s0 = s1;
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parseclosepath() {
          var s0, s1;
    
          s0 = peg$currPos;
          if (peg$c8.test(input.charAt(peg$currPos))) {
            s1 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s1 = peg$FAILED;
            { peg$fail(peg$c9); }
          }
          if (s1 !== peg$FAILED) {
            s1 = peg$c10();
          }
          s0 = s1;
    
          return s0;
        }
    
        function peg$parselineto() {
          var s0, s1, s2, s3;
    
          s0 = peg$currPos;
          if (peg$c11.test(input.charAt(peg$currPos))) {
            s1 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s1 = peg$FAILED;
            { peg$fail(peg$c12); }
          }
          if (s1 !== peg$FAILED) {
            s2 = [];
            s3 = peg$parsewsp();
            while (s3 !== peg$FAILED) {
              s2.push(s3);
              s3 = peg$parsewsp();
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parselineto_argument_sequence();
              if (s3 !== peg$FAILED) {
                s1 = peg$c13(s1, s3);
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parselineto_argument_sequence() {
          var s0, s1, s2, s3, s4;
    
          s0 = peg$currPos;
          s1 = peg$parsecoordinate_pair();
          if (s1 !== peg$FAILED) {
            s2 = peg$currPos;
            s3 = peg$parsecomma_wsp();
            if (s3 === peg$FAILED) {
              s3 = peg$c2;
            }
            if (s3 !== peg$FAILED) {
              s4 = peg$parselineto_argument_sequence();
              if (s4 !== peg$FAILED) {
                s3 = [s3, s4];
                s2 = s3;
              } else {
                peg$currPos = s2;
                s2 = peg$c0;
              }
            } else {
              peg$currPos = s2;
              s2 = peg$c0;
            }
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s1 = peg$c7(s1, s2);
              s0 = s1;
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsehorizontal_lineto() {
          var s0, s1, s2, s3;
    
          s0 = peg$currPos;
          if (peg$c14.test(input.charAt(peg$currPos))) {
            s1 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s1 = peg$FAILED;
            { peg$fail(peg$c15); }
          }
          if (s1 !== peg$FAILED) {
            s2 = [];
            s3 = peg$parsewsp();
            while (s3 !== peg$FAILED) {
              s2.push(s3);
              s3 = peg$parsewsp();
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parsecoordinate_sequence();
              if (s3 !== peg$FAILED) {
                s1 = peg$c16(s1, s3);
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsecoordinate_sequence() {
          var s0, s1, s2, s3, s4;
    
          s0 = peg$currPos;
          s1 = peg$parsecoordinate();
          if (s1 !== peg$FAILED) {
            s2 = peg$currPos;
            s3 = peg$parsecomma_wsp();
            if (s3 === peg$FAILED) {
              s3 = peg$c2;
            }
            if (s3 !== peg$FAILED) {
              s4 = peg$parsecoordinate_sequence();
              if (s4 !== peg$FAILED) {
                s3 = [s3, s4];
                s2 = s3;
              } else {
                peg$currPos = s2;
                s2 = peg$c0;
              }
            } else {
              peg$currPos = s2;
              s2 = peg$c0;
            }
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s1 = peg$c7(s1, s2);
              s0 = s1;
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsevertical_lineto() {
          var s0, s1, s2, s3;
    
          s0 = peg$currPos;
          if (peg$c17.test(input.charAt(peg$currPos))) {
            s1 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s1 = peg$FAILED;
            { peg$fail(peg$c18); }
          }
          if (s1 !== peg$FAILED) {
            s2 = [];
            s3 = peg$parsewsp();
            while (s3 !== peg$FAILED) {
              s2.push(s3);
              s3 = peg$parsewsp();
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parsecoordinate_sequence();
              if (s3 !== peg$FAILED) {
                s1 = peg$c19(s1, s3);
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsecurveto() {
          var s0, s1, s2, s3;
    
          s0 = peg$currPos;
          if (peg$c20.test(input.charAt(peg$currPos))) {
            s1 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s1 = peg$FAILED;
            { peg$fail(peg$c21); }
          }
          if (s1 !== peg$FAILED) {
            s2 = [];
            s3 = peg$parsewsp();
            while (s3 !== peg$FAILED) {
              s2.push(s3);
              s3 = peg$parsewsp();
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parsecurveto_argument_sequence();
              if (s3 !== peg$FAILED) {
                s1 = peg$c22(s1, s3);
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsecurveto_argument_sequence() {
          var s0, s1, s2, s3, s4;
    
          s0 = peg$currPos;
          s1 = peg$parsecurveto_argument();
          if (s1 !== peg$FAILED) {
            s2 = peg$currPos;
            s3 = peg$parsecomma_wsp();
            if (s3 === peg$FAILED) {
              s3 = peg$c2;
            }
            if (s3 !== peg$FAILED) {
              s4 = peg$parsecurveto_argument_sequence();
              if (s4 !== peg$FAILED) {
                s3 = [s3, s4];
                s2 = s3;
              } else {
                peg$currPos = s2;
                s2 = peg$c0;
              }
            } else {
              peg$currPos = s2;
              s2 = peg$c0;
            }
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s1 = peg$c7(s1, s2);
              s0 = s1;
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsecurveto_argument() {
          var s0, s1, s2, s3, s4, s5;
    
          s0 = peg$currPos;
          s1 = peg$parsecoordinate_pair();
          if (s1 !== peg$FAILED) {
            s2 = peg$parsecomma_wsp();
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parsecoordinate_pair();
              if (s3 !== peg$FAILED) {
                s4 = peg$parsecomma_wsp();
                if (s4 === peg$FAILED) {
                  s4 = peg$c2;
                }
                if (s4 !== peg$FAILED) {
                  s5 = peg$parsecoordinate_pair();
                  if (s5 !== peg$FAILED) {
                    s1 = peg$c23(s1, s3, s5);
                    s0 = s1;
                  } else {
                    peg$currPos = s0;
                    s0 = peg$c0;
                  }
                } else {
                  peg$currPos = s0;
                  s0 = peg$c0;
                }
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsesmooth_curveto() {
          var s0, s1, s2, s3;
    
          s0 = peg$currPos;
          if (peg$c24.test(input.charAt(peg$currPos))) {
            s1 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s1 = peg$FAILED;
            { peg$fail(peg$c25); }
          }
          if (s1 !== peg$FAILED) {
            s2 = [];
            s3 = peg$parsewsp();
            while (s3 !== peg$FAILED) {
              s2.push(s3);
              s3 = peg$parsewsp();
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parsesmooth_curveto_argument_sequence();
              if (s3 !== peg$FAILED) {
                s1 = peg$c26(s1, s3);
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsesmooth_curveto_argument_sequence() {
          var s0, s1, s2, s3, s4;
    
          s0 = peg$currPos;
          s1 = peg$parsesmooth_curveto_argument();
          if (s1 !== peg$FAILED) {
            s2 = peg$currPos;
            s3 = peg$parsecomma_wsp();
            if (s3 === peg$FAILED) {
              s3 = peg$c2;
            }
            if (s3 !== peg$FAILED) {
              s4 = peg$parsesmooth_curveto_argument_sequence();
              if (s4 !== peg$FAILED) {
                s3 = [s3, s4];
                s2 = s3;
              } else {
                peg$currPos = s2;
                s2 = peg$c0;
              }
            } else {
              peg$currPos = s2;
              s2 = peg$c0;
            }
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s1 = peg$c7(s1, s2);
              s0 = s1;
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsesmooth_curveto_argument() {
          var s0, s1, s2, s3;
    
          s0 = peg$currPos;
          s1 = peg$parsecoordinate_pair();
          if (s1 !== peg$FAILED) {
            s2 = peg$parsecomma_wsp();
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parsecoordinate_pair();
              if (s3 !== peg$FAILED) {
                s1 = peg$c27(s1, s3);
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsequadratic_bezier_curveto() {
          var s0, s1, s2, s3;
    
          s0 = peg$currPos;
          if (peg$c28.test(input.charAt(peg$currPos))) {
            s1 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s1 = peg$FAILED;
            { peg$fail(peg$c29); }
          }
          if (s1 !== peg$FAILED) {
            s2 = [];
            s3 = peg$parsewsp();
            while (s3 !== peg$FAILED) {
              s2.push(s3);
              s3 = peg$parsewsp();
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parsequadratic_bezier_curveto_argument_sequence();
              if (s3 !== peg$FAILED) {
                s1 = peg$c30(s1, s3);
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsequadratic_bezier_curveto_argument_sequence() {
          var s0, s1, s2, s3, s4;
    
          s0 = peg$currPos;
          s1 = peg$parsequadratic_bezier_curveto_argument();
          if (s1 !== peg$FAILED) {
            s2 = peg$currPos;
            s3 = peg$parsecomma_wsp();
            if (s3 === peg$FAILED) {
              s3 = peg$c2;
            }
            if (s3 !== peg$FAILED) {
              s4 = peg$parsequadratic_bezier_curveto_argument_sequence();
              if (s4 !== peg$FAILED) {
                s3 = [s3, s4];
                s2 = s3;
              } else {
                peg$currPos = s2;
                s2 = peg$c0;
              }
            } else {
              peg$currPos = s2;
              s2 = peg$c0;
            }
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s1 = peg$c7(s1, s2);
              s0 = s1;
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsequadratic_bezier_curveto_argument() {
          var s0, s1, s2, s3;
    
          s0 = peg$currPos;
          s1 = peg$parsecoordinate_pair();
          if (s1 !== peg$FAILED) {
            s2 = peg$parsecomma_wsp();
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parsecoordinate_pair();
              if (s3 !== peg$FAILED) {
                s1 = peg$c27(s1, s3);
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsesmooth_quadratic_bezier_curveto() {
          var s0, s1, s2, s3;
    
          s0 = peg$currPos;
          if (peg$c31.test(input.charAt(peg$currPos))) {
            s1 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s1 = peg$FAILED;
            { peg$fail(peg$c32); }
          }
          if (s1 !== peg$FAILED) {
            s2 = [];
            s3 = peg$parsewsp();
            while (s3 !== peg$FAILED) {
              s2.push(s3);
              s3 = peg$parsewsp();
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parsesmooth_quadratic_bezier_curveto_argument_sequence();
              if (s3 !== peg$FAILED) {
                s1 = peg$c33(s1, s3);
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsesmooth_quadratic_bezier_curveto_argument_sequence() {
          var s0, s1, s2, s3, s4;
    
          s0 = peg$currPos;
          s1 = peg$parsecoordinate_pair();
          if (s1 !== peg$FAILED) {
            s2 = peg$currPos;
            s3 = peg$parsecomma_wsp();
            if (s3 === peg$FAILED) {
              s3 = peg$c2;
            }
            if (s3 !== peg$FAILED) {
              s4 = peg$parsesmooth_quadratic_bezier_curveto_argument_sequence();
              if (s4 !== peg$FAILED) {
                s3 = [s3, s4];
                s2 = s3;
              } else {
                peg$currPos = s2;
                s2 = peg$c0;
              }
            } else {
              peg$currPos = s2;
              s2 = peg$c0;
            }
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s1 = peg$c7(s1, s2);
              s0 = s1;
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parseelliptical_arc() {
          var s0, s1, s2, s3;
    
          s0 = peg$currPos;
          if (peg$c34.test(input.charAt(peg$currPos))) {
            s1 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s1 = peg$FAILED;
            { peg$fail(peg$c35); }
          }
          if (s1 !== peg$FAILED) {
            s2 = [];
            s3 = peg$parsewsp();
            while (s3 !== peg$FAILED) {
              s2.push(s3);
              s3 = peg$parsewsp();
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parseelliptical_arc_argument_sequence();
              if (s3 !== peg$FAILED) {
                s1 = peg$c36(s1, s3);
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parseelliptical_arc_argument_sequence() {
          var s0, s1, s2, s3, s4;
    
          s0 = peg$currPos;
          s1 = peg$parseelliptical_arc_argument();
          if (s1 !== peg$FAILED) {
            s2 = peg$currPos;
            s3 = peg$parsecomma_wsp();
            if (s3 === peg$FAILED) {
              s3 = peg$c2;
            }
            if (s3 !== peg$FAILED) {
              s4 = peg$parseelliptical_arc_argument_sequence();
              if (s4 !== peg$FAILED) {
                s3 = [s3, s4];
                s2 = s3;
              } else {
                peg$currPos = s2;
                s2 = peg$c0;
              }
            } else {
              peg$currPos = s2;
              s2 = peg$c0;
            }
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s1 = peg$c7(s1, s2);
              s0 = s1;
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parseelliptical_arc_argument() {
          var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9, s10, s11;
    
          s0 = peg$currPos;
          s1 = peg$parsenonnegative_number();
          if (s1 !== peg$FAILED) {
            s2 = peg$parsecomma_wsp();
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parsenonnegative_number();
              if (s3 !== peg$FAILED) {
                s4 = peg$parsecomma_wsp();
                if (s4 === peg$FAILED) {
                  s4 = peg$c2;
                }
                if (s4 !== peg$FAILED) {
                  s5 = peg$parsenumber();
                  if (s5 !== peg$FAILED) {
                    s6 = peg$parsecomma_wsp();
                    if (s6 !== peg$FAILED) {
                      s7 = peg$parseflag();
                      if (s7 !== peg$FAILED) {
                        s8 = peg$parsecomma_wsp();
                        if (s8 === peg$FAILED) {
                          s8 = peg$c2;
                        }
                        if (s8 !== peg$FAILED) {
                          s9 = peg$parseflag();
                          if (s9 !== peg$FAILED) {
                            s10 = peg$parsecomma_wsp();
                            if (s10 === peg$FAILED) {
                              s10 = peg$c2;
                            }
                            if (s10 !== peg$FAILED) {
                              s11 = peg$parsecoordinate_pair();
                              if (s11 !== peg$FAILED) {
                                s1 = peg$c37(s1, s3, s5, s7, s9, s11);
                                s0 = s1;
                              } else {
                                peg$currPos = s0;
                                s0 = peg$c0;
                              }
                            } else {
                              peg$currPos = s0;
                              s0 = peg$c0;
                            }
                          } else {
                            peg$currPos = s0;
                            s0 = peg$c0;
                          }
                        } else {
                          peg$currPos = s0;
                          s0 = peg$c0;
                        }
                      } else {
                        peg$currPos = s0;
                        s0 = peg$c0;
                      }
                    } else {
                      peg$currPos = s0;
                      s0 = peg$c0;
                    }
                  } else {
                    peg$currPos = s0;
                    s0 = peg$c0;
                  }
                } else {
                  peg$currPos = s0;
                  s0 = peg$c0;
                }
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsecoordinate_pair() {
          var s0, s1, s2, s3;
    
          s0 = peg$currPos;
          s1 = peg$parsecoordinate();
          if (s1 !== peg$FAILED) {
            s2 = peg$parsecomma_wsp();
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parsecoordinate();
              if (s3 !== peg$FAILED) {
                s1 = peg$c38(s1, s3);
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsecoordinate() {
          var s0, s1;
    
          s0 = peg$currPos;
          s1 = peg$parsenumber();
          if (s1 !== peg$FAILED) {
            s1 = peg$c39(s1);
          }
          s0 = s1;
    
          return s0;
        }
    
        function peg$parsenonnegative_number() {
          var s0;
    
          s0 = peg$parsefloating_point_constant();
          if (s0 === peg$FAILED) {
            s0 = peg$parsedigit_sequence();
          }
    
          return s0;
        }
    
        function peg$parsenumber() {
          var s0, s1, s2;
    
          s0 = peg$currPos;
          s1 = peg$parsesign();
          if (s1 === peg$FAILED) {
            s1 = peg$c2;
          }
          if (s1 !== peg$FAILED) {
            s2 = peg$parsefloating_point_constant();
            if (s2 !== peg$FAILED) {
              s1 = [s1, s2];
              s0 = s1;
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
          if (s0 === peg$FAILED) {
            s0 = peg$currPos;
            s1 = peg$parsesign();
            if (s1 === peg$FAILED) {
              s1 = peg$c2;
            }
            if (s1 !== peg$FAILED) {
              s2 = peg$parsedigit_sequence();
              if (s2 !== peg$FAILED) {
                s1 = [s1, s2];
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          }
    
          return s0;
        }
    
        function peg$parseflag() {
          var s0;
    
          if (input.charCodeAt(peg$currPos) === 48) {
            s0 = peg$c40;
            peg$currPos++;
          } else {
            s0 = peg$FAILED;
            { peg$fail(peg$c41); }
          }
          if (s0 === peg$FAILED) {
            if (input.charCodeAt(peg$currPos) === 49) {
              s0 = peg$c42;
              peg$currPos++;
            } else {
              s0 = peg$FAILED;
              { peg$fail(peg$c43); }
            }
          }
    
          return s0;
        }
    
        function peg$parsecomma_wsp() {
          var s0, s1, s2, s3, s4;
    
          s0 = peg$currPos;
          s1 = [];
          s2 = peg$parsewsp();
          if (s2 !== peg$FAILED) {
            while (s2 !== peg$FAILED) {
              s1.push(s2);
              s2 = peg$parsewsp();
            }
          } else {
            s1 = peg$c0;
          }
          if (s1 !== peg$FAILED) {
            s2 = peg$parsecomma();
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s3 = [];
              s4 = peg$parsewsp();
              while (s4 !== peg$FAILED) {
                s3.push(s4);
                s4 = peg$parsewsp();
              }
              if (s3 !== peg$FAILED) {
                s1 = [s1, s2, s3];
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
          if (s0 === peg$FAILED) {
            s0 = peg$currPos;
            s1 = peg$parsecomma();
            if (s1 !== peg$FAILED) {
              s2 = [];
              s3 = peg$parsewsp();
              while (s3 !== peg$FAILED) {
                s2.push(s3);
                s3 = peg$parsewsp();
              }
              if (s2 !== peg$FAILED) {
                s1 = [s1, s2];
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          }
    
          return s0;
        }
    
        function peg$parsecomma() {
          var s0;
    
          if (input.charCodeAt(peg$currPos) === 44) {
            s0 = peg$c44;
            peg$currPos++;
          } else {
            s0 = peg$FAILED;
            { peg$fail(peg$c45); }
          }
    
          return s0;
        }
    
        function peg$parsefloating_point_constant() {
          var s0, s1, s2;
    
          s0 = peg$currPos;
          s1 = peg$parsefractional_constant();
          if (s1 !== peg$FAILED) {
            s2 = peg$parseexponent();
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s1 = [s1, s2];
              s0 = s1;
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
          if (s0 === peg$FAILED) {
            s0 = peg$currPos;
            s1 = peg$parsedigit_sequence();
            if (s1 !== peg$FAILED) {
              s2 = peg$parseexponent();
              if (s2 !== peg$FAILED) {
                s1 = [s1, s2];
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          }
    
          return s0;
        }
    
        function peg$parsefractional_constant() {
          var s0, s1, s2, s3;
    
          s0 = peg$currPos;
          s1 = peg$parsedigit_sequence();
          if (s1 === peg$FAILED) {
            s1 = peg$c2;
          }
          if (s1 !== peg$FAILED) {
            if (input.charCodeAt(peg$currPos) === 46) {
              s2 = peg$c46;
              peg$currPos++;
            } else {
              s2 = peg$FAILED;
              { peg$fail(peg$c47); }
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parsedigit_sequence();
              if (s3 !== peg$FAILED) {
                s1 = [s1, s2, s3];
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
          if (s0 === peg$FAILED) {
            s0 = peg$currPos;
            s1 = peg$parsedigit_sequence();
            if (s1 !== peg$FAILED) {
              if (input.charCodeAt(peg$currPos) === 46) {
                s2 = peg$c46;
                peg$currPos++;
              } else {
                s2 = peg$FAILED;
                { peg$fail(peg$c47); }
              }
              if (s2 !== peg$FAILED) {
                s1 = [s1, s2];
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          }
    
          return s0;
        }
    
        function peg$parseexponent() {
          var s0, s1, s2, s3;
    
          s0 = peg$currPos;
          if (peg$c48.test(input.charAt(peg$currPos))) {
            s1 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s1 = peg$FAILED;
            { peg$fail(peg$c49); }
          }
          if (s1 !== peg$FAILED) {
            s2 = peg$parsesign();
            if (s2 === peg$FAILED) {
              s2 = peg$c2;
            }
            if (s2 !== peg$FAILED) {
              s3 = peg$parsedigit_sequence();
              if (s3 !== peg$FAILED) {
                s1 = [s1, s2, s3];
                s0 = s1;
              } else {
                peg$currPos = s0;
                s0 = peg$c0;
              }
            } else {
              peg$currPos = s0;
              s0 = peg$c0;
            }
          } else {
            peg$currPos = s0;
            s0 = peg$c0;
          }
    
          return s0;
        }
    
        function peg$parsesign() {
          var s0;
    
          if (input.charCodeAt(peg$currPos) === 43) {
            s0 = peg$c50;
            peg$currPos++;
          } else {
            s0 = peg$FAILED;
            { peg$fail(peg$c51); }
          }
          if (s0 === peg$FAILED) {
            if (input.charCodeAt(peg$currPos) === 45) {
              s0 = peg$c52;
              peg$currPos++;
            } else {
              s0 = peg$FAILED;
              { peg$fail(peg$c53); }
            }
          }
    
          return s0;
        }
    
        function peg$parsedigit_sequence() {
          var s0, s1, s2;
    
          s0 = peg$currPos;
          s1 = [];
          if (peg$c54.test(input.charAt(peg$currPos))) {
            s2 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s2 = peg$FAILED;
            { peg$fail(peg$c55); }
          }
          if (s2 !== peg$FAILED) {
            while (s2 !== peg$FAILED) {
              s1.push(s2);
              if (peg$c54.test(input.charAt(peg$currPos))) {
                s2 = input.charAt(peg$currPos);
                peg$currPos++;
              } else {
                s2 = peg$FAILED;
                { peg$fail(peg$c55); }
              }
            }
          } else {
            s1 = peg$c0;
          }
          if (s1 !== peg$FAILED) {
            s1 = peg$c56(s1);
          }
          s0 = s1;
    
          return s0;
        }
    
        function peg$parsewsp() {
          var s0;
    
          if (peg$c57.test(input.charAt(peg$currPos))) {
            s0 = input.charAt(peg$currPos);
            peg$currPos++;
          } else {
            s0 = peg$FAILED;
            { peg$fail(peg$c58); }
          }
    
          return s0;
        }
    
    
          // The last coordinate we are at in the path. In absolute coords.
          var lastCoord = [0, 0];
          // The last control point we encountered in the path. In absolute coords.
          var lastControl = [0, 0];
          // The list of operations we've parsed so far.
          var ops = [];
          // Have we parsed the first sub-path yet?
          var firstSubPath = true;
          // The letter of the last parsed command.
          var lastCh = '';
    
          // Flatten an array.
          function flatten(a) {
            var flat = [];
            for (var i = 0; i < a.length; i++) {
              if (a[i] instanceof Array) {
                flat.push.apply(flat, flatten(a[i]));
              } else {
                flat.push(a[i]);
              }
            }
            return flat;
          }
    
          // Convert a position into an absolute position.
          function makeAbsolute(c, coord) {
            if ('mlazhvcsqt'.indexOf(c) === -1) {
              lastCoord = coord;
            } else {
              lastCoord[0] += coord[0];
              lastCoord[1] += coord[1];
            }
            lastCh = c;
            return lastCoord.slice(0);
          }
    
          // Convert a sequence of coordinates into absolute coordinates.
          //
          // For arguments that take multiple coord pairs, such as bezier.
          function makeAbsoluteMultiple(c, seq) {
            var r = [];
            var lastPosCopy = lastCoord.slice(0);
            for (var i=0; i < seq.length; i+=2) {
              // Only the last point should update lastCoord.
              lastCoord = lastPosCopy.slice(0);
              var coord = makeAbsolute(c, seq.slice(i, i+2));
              r = r.concat(coord);
              // Record the last control point, it might be needed for
              // shorthand operations.
              if (i == seq.length-4) {
                lastControl = coord.slice(0);
              }
            }
            return r;
          }
    
          // Find the reflection of the last control point over
          // the last postion in the path.
          function makeReflected() {
            if ('CcSsQqTt'.indexOf(lastCh) == -1) {
              lastControl = lastCoord.slice(0);
            }
            // reflected = 2*lastCoord - lastControl
            // Note the result is absolute, not relative.
            var r = [0, 0];
            r[0] = 2*lastCoord[0] - lastControl[0];
            r[1] = 2*lastCoord[1] - lastControl[1];
            return r;
          }
    
          function makeAbsoluteFromX(c, x) {
            var coord = [x, 0];
            if (c == 'H') {
              coord[1] = lastCoord[1];
            }
            return makeAbsolute(c, coord);
          }
    
          function makeAbsoluteFromY(c, y) {
            var coord = [0, y];
            if (c == 'V') {
              coord[0] = lastCoord[0];
            }
            return makeAbsolute(c, coord);
          }
    
          function concatSequence(one, rest) {
            var r = [one];
            if (rest && rest.length > 1) {
              var rem = rest[1];
              for (var i = 0; i < rem.length; i++) {
                r.push(rem[i]);
              }
            }
            return r;
          }
    
          function mag(v) {
            return Math.sqrt(Math.pow(v[0], 2) + Math.pow(v[1], 2));
          }
    
          function dot(u, v) {
            return (u[0]*v[0] + u[1]*v[1]);
          }
    
          function ratio(u, v) {
            return dot(u,v) / (mag(u)*mag(v))
          }

          function clamp(value, min, max) {
            return Math.min(Math.max(val, min),max);
          }
    
          function angle(u, v) {
            var sign = 1.0;
            if ((u[0]*v[1] - u[1]*v[0]) < 0) {
              sign = -1.0;
            }
            return sign * Math.acos(clamp(ratio(u,v)), -1, 1);
          }
    
          function rotClockwise(v, angle) {
            var cost = Math.cos(angle);
            var sint = Math.sin(angle);
            return [cost*v[0] + sint*v[1], -1 * sint*v[0] + cost*v[1]];
          }
    
          function rotCounterClockwise(v, angle) {
            var cost = Math.cos(angle);
            var sint = Math.sin(angle);
            return [cost*v[0] - sint*v[1], sint*v[0] + cost*v[1]];
          }
    
          function midPoint(u, v) {
            return [(u[0] - v[0])/2.0, (u[1] - v[1])/2.0];
          }
    
          function meanVec(u, v) {
            return [(u[0] + v[0])/2.0, (u[1] + v[1])/2.0];
          }
    
          function pointMul(u, v) {
            return [u[0]*v[0], u[1]*v[1]];
          }
    
          function scale(c, v) {
            return [c*v[0], c*v[1]];
          }
    
          function sum(u, v) {
            return [u[0] + v[0], u[1] + v[1]];
          }
    
          // Convert an SVG elliptical arc to a series of canvas commands.
          //
          // x1, x2: start and stop coordinates of the ellipse.
          // rx, ry: radii of the ellipse.
          // phi: rotation of the ellipse.
          // fA: large arc flag.
          // fS: sweep flag.
          function ellipseFromEllipticalArc(x1, rx, ry, phi, fA, fS, x2) {
            // Convert from endpoint to center parametrization, as detailed in:
            //   http://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
            if (rx == 0 || ry == 0) {
              ops.push({type: 'lineTo', args: x2});
              return;
            }
            var phi = phi * (Math.PI / 180.0);
            rx = Math.abs(rx);
            ry = Math.abs(ry);
            var xPrime = rotClockwise(midPoint(x1, x2), phi);                // F.6.5.1
            var xPrime2 = pointMul(xPrime, xPrime);
            var rx2 = Math.pow(rx, 2);
            var ry2 = Math.pow(ry, 2);
    
            var lambda = Math.sqrt(xPrime2[0]/rx2 + xPrime2[1]/ry2);
            if (lambda > 1) {
              rx *= lambda;
              ry *= lambda;
              rx2 = Math.pow(rx, 2);
              ry2 = Math.pow(ry, 2);
            }
            var factor = Math.sqrt(Math.abs(rx2*ry2 - rx2*xPrime2[1] - ry2*xPrime2[0]) /
              (rx2*xPrime2[1] + ry2*xPrime2[0]));
            if (fA == fS) {
              factor *= -1.0;
            }
            var cPrime = scale(factor, [rx*xPrime[1]/ry, -ry*xPrime[0]/rx]); // F.6.5.2
            var c = sum(rotCounterClockwise(cPrime, phi), meanVec(x1, x2));  // F.6.5.3
            var x1UnitVector = [(xPrime[0] - cPrime[0])/rx, (xPrime[1] - cPrime[1])/ry];
            var x2UnitVector = [(-1.0*xPrime[0] - cPrime[0])/rx, (-1.0*xPrime[1] - cPrime[1])/ry];
            var theta = angle([1, 0], x1UnitVector);                         // F.6.5.5
            var deltaTheta = angle(x1UnitVector, x2UnitVector);              // F.6.5.6
            var start = theta;
            var end = theta+deltaTheta;
            ops.push(
              {type: 'save', args: []},
              {type: 'translate', args: [c[0], c[1]]},
              {type: 'rotate', args: [phi]},
              {type: 'scale', args: [rx, ry]},
              {type: 'arc', args: [0, 0, 1, start, end, 1-fS]},
              {type: 'restore', args: []}
              );
          }
    
    
        peg$result = peg$startRuleFunction();
    
        if (peg$result !== peg$FAILED && peg$currPos === input.length) {
          return peg$result;
        } else {
          if (peg$result !== peg$FAILED && peg$currPos < input.length) {
            peg$fail({ type: "end", description: "end of input" });
          }
    
          throw peg$buildException(null, peg$maxFailExpected, peg$maxFailPos);
        }
      }
    
      return {
        SyntaxError: SyntaxError,
        parse:       parse
      };
    })();

    function Path_(arg) {
      this.ops_ = [];
      if (arg == undefined) {
        return;
      }
      if (typeof arg == 'string') {
        try {
          this.ops_ = parser.parse(arg);
        } catch(e) {
          // Treat an invalid SVG path as an empty path.
        }
      } else if (arg.hasOwnProperty('ops_')) {
        this.ops_ = arg.ops_.slice(0);
      } else {
        throw 'Error: ' + typeof arg + 'is not a valid argument to Path';
      }
    }
    // TODO(jcgregorio) test for arcTo and implement via something.


    // Path methods that map simply to the CanvasRenderingContext2D.
    var simple_mapping = [
      'closePath',
      'moveTo',
      'lineTo',
      'quadraticCurveTo',
      'bezierCurveTo',
      'rect',
      'arc',
      'arcTo',
      'ellipse',
      'isPointInPath',
      'isPointInStroke',
      ];

    function createFunction(name) {
      return function() {
        this.ops_.push({type: name, args: Array.prototype.slice.call(arguments, 0)});
      };
    }

    // Add simple_mapping methods to Path2D.
    for (var i=0; i<simple_mapping.length; i++) {
      var name = simple_mapping[i];
      Path_.prototype[name] = createFunction(name);
    }

    Path_.prototype['addPath'] = function(path, tr) {
      var hasTx = false;
      if (tr
          && tr.a != undefined
          && tr.b != undefined
          && tr.c != undefined
          && tr.d != undefined
          && tr.e != undefined
          && tr.f != undefined) {
        hasTx = true;
        this.ops_.push({type: 'save', args: []});
        this.ops_.push({type: 'transform', args: [tr.a, tr.b, tr.c, tr.d, tr.e, tr.f]});
      }
      this.ops_ = this.ops_.concat(path.ops_);
      if (hasTx) {
        this.ops_.push({type: 'restore', args: []});
      }
    };

    var original_fill = CanvasRenderingContext2D.prototype.fill;
    var original_stroke = CanvasRenderingContext2D.prototype.stroke;
    var original_clip = CanvasRenderingContext2D.prototype.clip;
    var original_is_point_in_path = CanvasRenderingContext2D.prototype.isPointInPath;
    var original_is_point_in_stroke = CanvasRenderingContext2D.prototype.isPointInStroke;

    // Replace methods on CanvasRenderingContext2D with ones that understand Path2D.
    CanvasRenderingContext2D.prototype.fill = function(arg) {
      if (arg instanceof Path_) {
        this.beginPath();
        for (var i = 0, len = arg.ops_.length; i < len; i++) {
          var op = arg.ops_[i];
          CanvasRenderingContext2D.prototype[op.type].apply(this, op.args);
        }
        original_fill.apply(this, Array.prototype.slice.call(arguments, 1));
      } else {
        original_fill.apply(this, arguments);
      }
    };

    CanvasRenderingContext2D.prototype.stroke = function(arg) {
      if (arg instanceof Path_) {
        this.beginPath();
        for (var i = 0, len = arg.ops_.length; i < len; i++) {
          var op = arg.ops_[i];
          CanvasRenderingContext2D.prototype[op.type].apply(this, op.args);
        }
        original_stroke.call(this);
      } else {
        original_stroke.call(this);
      }
    };

    CanvasRenderingContext2D.prototype.clip = function(arg) {
      if (arg instanceof Path_) {
        // Note that we don't save and restore the context state, since the
        // clip region is part of the state. Not really a problem since the
        // HTML 5 spec doesn't say that clip(path) doesn't affect the current
        // path.
        this.beginPath();
        for (var i = 0, len = arg.ops_.length; i < len; i++) {
          var op = arg.ops_[i];
          CanvasRenderingContext2D.prototype[op.type].apply(this, op.args);
        }
        original_clip.apply(this, Array.prototype.slice.call(arguments, 1));
      } else {
        original_clip.apply(this, arguments);
      }
    };

    CanvasRenderingContext2D.prototype.isPointInPath = function(arg) {
      if (arg instanceof Path_) {
        this.beginPath();
        for (var i = 0, len = arg.ops_.length; i < len; i++) {
          var op = arg.ops_[i];
          CanvasRenderingContext2D.prototype[op.type].apply(this, op.args);
        }
        return original_is_point_in_path.apply(this, Array.prototype.slice.call(arguments, 1));
      } else {
        return original_is_point_in_path.apply(this, arguments);
      }
    };
    CanvasRenderingContext2D.prototype.isPointInStroke = function(arg) {
      if (arg instanceof Path_) {
        this.beginPath();
        for (var i = 0, len = arg.ops_.length; i < len; i++) {
          var op = arg.ops_[i];
          CanvasRenderingContext2D.prototype[op.type].apply(this, op.args);
        }
        return original_is_point_in_stroke.apply(this, Array.prototype.slice.call(arguments, 1));
      } else {
        return original_is_point_in_stroke.apply(this, arguments);
      }
    };

    // Set up externs.
    window.Path2D = Path_;
  })();
}

})(
  typeof CanvasRenderingContext2D === "undefined" ? undefined : CanvasRenderingContext2D,
  typeof require === "undefined" ? undefined : require
);

// https://www.colordic.org/w/

var Colors = [
  '#00552e',
  '#028760',
  '#884898',
  '#2b2b2b',
  '#f8b500',
  '#eb9b6f',
  '#3eb370',
  '#9fa0a0',
  '#706caa',
  '#dd7a56',
  '#ea5506',
  '#fddea5',
  '#f2f2b0',
  '#165e83',
  '#b8d200',
  '#2792c3',
  '#dccb18',
  '#e95295',
  '#4f455c',
  '#00a381',
  '#e6b422',
];

function toHighDPI(canvas) {
  const ctx = canvas.getContext('2d');
  const { width, height } = canvas;
  const { devicePixelRatio = 1 } = window;
  canvas.width = width * devicePixelRatio;
  canvas.height = height * devicePixelRatio;
  canvas.style.width = `${width}px`;
  canvas.style.height = `${height}px`;
  ctx.scale(devicePixelRatio, devicePixelRatio);
}

function toRadians(angle) {
  return angle / 180 * Math.PI;
}

function drawControlPoint(ctx, x, y) {
  ctx.save();
  ctx.fillStyle = 'red';
  ctx.fillRect(x - 2, y - 2, 4, 4);
  ctx.restore();
}

const array = new Uint32Array(1);

// https://stackoverflow.com/questions/13694626/generating-random-numbers-0-to-1-with-crypto-generatevalues
function random() {
  window.crypto.getRandomValues(array);
  return array[0] * (2 ** -32);
}

function randomColor() {
  const randomValue = random();
  const index = Math.floor(randomValue * Colors.length);
  return Colors[index];
}

// https://developer.mozilla.org/en-US/docs/Web/HTML/CORS_enabled_image
function loadImage(src) {
  return new Promise((resolve, reject) => {
    const img = new Image();
    img.crossOrigin = 'Anonymous';
    img.src = src;
    img.onload = () => {
      resolve(img);
    };
    img.onerror = (e) => {
      reject(e);
    };
  });
}

// https://developers.google.com/web/ilt/pwa/working-with-the-fetch-api
// https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
function loadImageBitmap(src) {
  return fetch(src, {
    mode: 'cors',
    headers: {
      'Access-Control-Allow-Origin': '*',
    },
  }).then((response) => {
    if (response.ok) {
      return response.blob();
    }
    throw new Error(response.statusText);
  }).then(blob => createImageBitmap(blob));
}

const colorCanvas = document.createElement('canvas');
colorCanvas.width = 1;
colorCanvas.heith = 1;
const colorCtx = colorCanvas.getContext('2d');

function getColorValue(color) {
  colorCtx.clearRect(0, 0, 1, 1);
  colorCtx.fillStyle = color;
  colorCtx.fillRect(0, 0, 1, 1);
  const { data } = colorCtx.getImageData(0, 0, 1, 1);
  const value = {
    r: data[0],
    g: data[1],
    b: data[2],
    a: data[3],
    toString() {
      return `rgba(${this.r}, ${this.g}, ${this.b}, ${this.a})`;
    },
  };
  return value;
}

function getColorImage(image, color) {
  const { width, height } = image;
  const canvas = document.createElement('canvas');
  canvas.width = width;
  canvas.height = height;
  const ctx = canvas.getContext('2d');
  ctx.drawImage(image, 0, 0, width, height);
  const imageData = ctx.getImageData(0, 0, width, height);
  const pix = imageData.data;
  const colorValue = getColorValue(color);
  for (let i = 0, n = pix.length; i < n; i += 4) {
    const sourceColor = { r: pix[i + 0], g: pix[i + 1], b: pix[i + 2] };
    const brightness = sourceColor.r * 0.30 + sourceColor.g * 0.59 + sourceColor.b * 0.11;
    const result = {
      r: Math.floor(colorValue.r * brightness / 255),
      g: Math.floor(colorValue.g * brightness / 255),
      b: Math.floor(colorValue.b * brightness / 255),
    };
    pix[i + 0] = result.r;
    pix[i + 1] = result.g;
    pix[i + 2] = result.b;
  }
  ctx.putImageData(imageData, 0, 0);
  return canvas;
}

function getClientPoint(e) {
  return {
    x: (e.touches ? e.touches[0] : e).clientX,
    y: (e.touches ? e.touches[0] : e).clientY,
  };
}

function getPointAt(view, e) {
  const bound = view.getBoundingClientRect();
  const point = getClientPoint(e);
  return {
    x: point.x - bound.left,
    y: point.y - bound.top,
  };
}

function delay(time) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve();
    }, time);
  });
}

var Clear = {
  clearRect: {
    clear: false,
    draw() {
      const { ctx } = this;
      ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
      ctx.save();
      ctx.strokeStyle = randomColor();
      ctx.beginPath();
      ctx.rect(10, 10, 100, 200);
      ctx.stroke();
      ctx.restore();
    },
  },
  canvas_width_with_bug: {
    clear: false,
    draw() {
      const { ctx } = this;
      ctx.canvas.width = ctx.canvas.width;
      ctx.save();
      ctx.strokeStyle = randomColor();
      ctx.beginPath();
      ctx.rect(10, 10, 100, 200);
      ctx.stroke();
      ctx.restore();
    },
  },
  canvas_width: {
    clear: false,
    draw() {
      const { ctx } = this;
      ctx.canvas.width = ctx.canvas.width;
      ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
      ctx.save();
      ctx.strokeStyle = randomColor();
      ctx.beginPath();
      ctx.rect(10, 10, 100, 200);
      ctx.stroke();
      ctx.restore();
    },
  },
};

var Path = {
  moveTo_lineTo: {
    config: {
      x: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      y: {
        value: 10,
        min: 0,
        max: 500,
        step: 1,
      },
      lineWidth: {
        value: 10,
        min: 0,
        max: 20,
        step: 1,
      },
      lineCap: {
        value: 'butt',
        values: [
          'butt',
          'round',
          'square',
        ],
      },
      lineJoin: {
        value: 'miter',
        values: [
          'round',
          'bevel',
          'miter',
        ],
      },
      miterLimit: {
        value: 10,
        min: 0,
        max: 30,
        step: 1,
      },
      lineDash: '',
      lineDashOffset: {
        value: 0,
        min: -100,
        max: 100,
        step: 1,
      },
      strokeStyle: {
        color: randomColor(),
      },
    },
    draw() {
      const { ctx, config } = this;
      ctx.strokeStyle = config.strokeStyle.color;
      ctx.lineWidth = config.lineWidth.value;
      ctx.lineCap = config.lineCap.value;
      ctx.lineJoin = config.lineJoin.value;
      ctx.miterLimit = config.miterLimit.value;
      const lineDash = config.lineDash.split(',');
      if (lineDash.length > 1) {
        ctx.setLineDash(lineDash);
      }
      ctx.lineDashOffset = config.lineDashOffset.value;
      ctx.beginPath();
      ctx.moveTo(config.x.value, config.y.value);
      ctx.lineTo(300, 210);
      ctx.lineTo(100, 210);
      ctx.lineTo(200, 10);
      ctx.moveTo(100, 100);
      ctx.lineTo(200, 100);
      ctx.stroke();
    },
  },
  closePath: {
    config: {
      lineWidth: {
        value: 5,
        min: 0,
        max: 10,
        step: 1,
      },
      strokeStyle: {
        color: randomColor(),
      },
      closePath: true,
    },
    draw() {
      const { ctx, config } = this;
      ctx.strokeStyle = config.strokeStyle.color;
      ctx.lineWidth = config.lineWidth.value;
      ctx.beginPath();
      ctx.moveTo(200, 10);
      ctx.lineTo(300, 210);
      ctx.lineTo(100, 210);
      if (config.closePath) {
        ctx.closePath();
      }
      ctx.stroke();
    },
  },
  fill: {
    config: {
      fillStyle: {
        color: randomColor(),
      },
    },
    draw() {
      const { ctx, config } = this;
      ctx.fillStyle = config.fillStyle.color;
      ctx.beginPath();
      ctx.moveTo(200, 10);
      ctx.lineTo(300, 210);
      ctx.lineTo(100, 210);
      ctx.fill();
    },
  },
  fill_stroke: {
    config: {
      lineWidth: {
        value: 5,
        min: 0,
        max: 10,
        step: 1,
      },
      strokeStyle: {
        color: randomColor(),
      },
      fillStyle: {
        color: randomColor(),
      },
      fill: true,
      stroke: true,
    },
    draw() {
      const { ctx, config } = this;
      ctx.fillStyle = config.fillStyle.color;
      ctx.strokeStyle = config.strokeStyle.color;
      ctx.lineWidth = config.lineWidth.value;
      ctx.beginPath();
      ctx.moveTo(200, 10);
      ctx.lineTo(300, 210);
      ctx.lineTo(100, 210);
      if (config.fill) {
        ctx.fill();
      }
      if (config.stroke) {
        ctx.stroke();
      }
    },
  },
  stroke_fill: {
    config: {
      lineWidth: {
        value: 5,
        min: 0,
        max: 10,
        step: 1,
      },
      strokeStyle: {
        color: randomColor(),
      },
      fillStyle: {
        color: randomColor(),
      },
      fill: true,
      stroke: true,
    },
    draw() {
      const { ctx, config } = this;
      ctx.fillStyle = config.fillStyle.color;
      ctx.strokeStyle = config.strokeStyle.color;
      ctx.lineWidth = config.lineWidth.value;
      ctx.beginPath();
      ctx.moveTo(200, 10);
      ctx.lineTo(300, 210);
      ctx.lineTo(100, 210);
      if (config.stroke) {
        ctx.stroke();
      }
      if (config.fill) {
        ctx.fill();
      }
    },
  },
  async without_beginPath(ctx) {
    ctx.strokeStyle = randomColor();
    ctx.lineWidth = 5;
    ctx.beginPath();
    ctx.moveTo(200, 10);
    ctx.lineTo(300, 210);
    ctx.lineTo(100, 210);
    ctx.stroke();
    await delay(2000);
    if (ctx.canvas._lesson !== 'Path_without_beginPath') {
      return;
    }
    ctx.strokeStyle = randomColor();
    ctx.rect(10, 10, 100, 100);
    ctx.stroke();
  },
  async with_beginPath(ctx) {
    ctx.strokeStyle = randomColor();
    ctx.lineWidth = 5;
    ctx.beginPath();
    ctx.moveTo(200, 10);
    ctx.lineTo(300, 210);
    ctx.lineTo(100, 210);
    ctx.stroke();
    await delay(2000);
    if (ctx.canvas._lesson !== 'Path_with_beginPath') {
      return;
    }
    ctx.strokeStyle = randomColor();
    ctx.beginPath();
    ctx.rect(10, 10, 100, 100);
    ctx.stroke();
  },
  curve: {
    config: {
      color: {
        color: randomColor(),
      },
      cpx: {
        value: 160,
        min: 0,
        max: 500,
        step: 1,
      },
      cpy: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      cp1x: {
        value: 85,
        min: 0,
        max: 500,
        step: 1,
      },
      cp1y: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      cp2x: {
        value: 235,
        min: 0,
        max: 500,
        step: 1,
      },
      cp2y: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
    },
    draw() {
      const { ctx, config } = this;

      ctx.strokeStyle = config.color.color;
      ctx.beginPath();
      const p1 = { x: 10, y: 100 };
      const p2 = { x: 310, y: 100 };
      const {
        cpx, cpy, cp1x, cp1y, cp2x, cp2y,
      } = config;
      ctx.moveTo(p1.x, p1.y);
      ctx.quadraticCurveTo(cpx.value, cpy.value, p2.x, p2.y);
      ctx.moveTo(p1.x, p1.y);
      ctx.bezierCurveTo(cp1x.value, cp1y.value, cp2x.value, cp2y.value, p2.x, p2.y);
      ctx.stroke();
      drawControlPoint(ctx, cpx.value, cpy.value);
      drawControlPoint(ctx, cp1x.value, cp1y.value);
      drawControlPoint(ctx, cp2x.value, cp2y.value);
    },
  },
  arcTo: {
    config: {
      color1: {
        color: randomColor(),
      },
      color2: {
        color: randomColor(),
      },
      x0: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      y0: {
        value: 50,
        min: 0,
        max: 500,
        step: 1,
      },
      x1: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      y1: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      x2: {
        value: 0,
        min: 0,
        max: 500,
        step: 1,
      },
      y2: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      radius: {
        value: 100,
        min: 0,
        max: 500,
        step: 1,
      },
    },
    draw() {
      const { ctx, config } = this;

      ctx.lineWidth = 3;
      ctx.strokeStyle = config.color1.color;
      ctx.beginPath();
      ctx.arc(100, 100, 100, 0, Math.PI * 2);
      ctx.rect(0, 0, 200, 200);
      ctx.stroke();
      ctx.strokeStyle = config.color2.color;
      ctx.beginPath();
      const {
        x0, y0, x1, y1, x2, y2, radius,
      } = config;
      ctx.moveTo(x0.value, y0.value);
      // x1, y1, x2, y2, radius
      ctx.arcTo(x1.value, y1.value, x2.value, y2.value, radius.value);
      ctx.lineTo(150, 200);
      ctx.stroke();
      drawControlPoint(ctx, x0.value, y0.value);
      drawControlPoint(ctx, x1.value, y1.value);
      drawControlPoint(ctx, x2.value, y2.value);
    },
  },
  arc: {
    config: {
      color: {
        color: randomColor(),
      },
      cx: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      cy: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      radius: {
        value: 80,
        min: 0,
        max: 500,
        step: 1,
      },
      startAngle: {
        value: 0,
        min: -360,
        max: 360,
        step: 1,
      },
      endAngle: {
        value: 90,
        min: -360,
        max: 360,
        step: 1,
      },
      anticlockwise: true,
    },
    draw() {
      const { ctx, config } = this;

      ctx.strokeStyle = config.color.color;
      ctx.beginPath();
      const {
        cx, cy, radius, startAngle, endAngle, anticlockwise,
      } = config;
      ctx.moveTo(cx.value, cy.value);
      // x, y, radius, startAngle, endAngle, anticlockwise
      ctx.arc(cx.value, cy.value, radius.value,
        toRadians(startAngle.value), toRadians(endAngle.value), anticlockwise);
      ctx.stroke();
      drawControlPoint(ctx, cx.value, cy.value);
    },
  },
  arc_without_subpath(ctx) {
    ctx.strokeStyle = randomColor();
    ctx.beginPath();
    ctx.arc(300, 300, 80, 0, Math.PI * 2);
    ctx.moveTo(20, 100);
    // x, y, radius, startAngle, endAngle, anticlockwise
    ctx.arc(100, 100, 80, Math.PI / 2, Math.PI, false);
    ctx.moveTo(300, 180);
    ctx.arc(300, 100, 80, Math.PI / 2, Math.PI, false);
    ctx.arc(100, 300, 80, 0, Math.PI * 2);
    ctx.stroke();
  },
  ellipse: {
    config: {
      color: {
        color: randomColor(),
      },
      cx: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      cy: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      radiusX: {
        value: 100,
        min: 0,
        max: 500,
        step: 1,
      },
      radiusY: {
        value: 60,
        min: 0,
        max: 500,
        step: 1,
      },
      rotation: {
        value: 15,
        min: -360,
        max: 360,
        step: 1,
      },
      startAngle: {
        value: 0,
        min: -360,
        max: 360,
        step: 1,
      },
      endAngle: {
        value: 360,
        min: -360,
        max: 360,
        step: 1,
      },
      anticlockwise: false,
    },
    draw() {
      const { ctx, config } = this;

      ctx.strokeStyle = config.color.color;
      ctx.beginPath();
      const {
        cx, cy, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise,
      } = config;
      ctx.moveTo(cx.value, cy.value);
      // x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise
      ctx.ellipse(cx.value, cy.value, radiusX.value, radiusY.value, toRadians(rotation.value),
        toRadians(startAngle.value), toRadians(endAngle.value), anticlockwise);
      ctx.stroke();
      drawControlPoint(ctx, cx.value, cy.value);
    },
  },
  rect(ctx) {
    ctx.beginPath();
    ctx.moveTo(20, 20);
    ctx.lineTo(200, 20);
    ctx.rect(30, 10, 100, 200);
    ctx.strokeStyle = randomColor();
    ctx.strokeRect(100, 50, 100, 100);
    ctx.lineTo(300, 300);
    ctx.strokeStyle = randomColor();
    ctx.stroke();
  },
  clip: {
    config: {
      clipRect: true,
      clipCircle: true,
      clipTwoShape: false,
      color: {
        color: randomColor(),
      },
    },
    draw() {
      const { ctx, config } = this;
      ctx.save();

      if (config.clipTwoShape) {
        if (config.clipRect || config.clipCircle) {
          ctx.beginPath();
          if (config.clipRect) {
            ctx.rect(20, 20, 60, 60);
          }

          if (config.clipCircle) {
            ctx.arc(70, 70, 50, 0, Math.PI * 2);
          }
          ctx.clip();
        }
      } else {
        if (config.clipRect) {
          ctx.beginPath();
          ctx.rect(20, 20, 60, 60);
          ctx.clip();
        }

        if (config.clipCircle) {
          ctx.beginPath();
          ctx.arc(70, 70, 50, 0, Math.PI * 2);
          ctx.clip();
        }
      }

      ctx.beginPath();
      ctx.fillStyle = config.color.color;
      ctx.rect(0, 0, 100, 100);
      ctx.fill();
      ctx.restore();

      ctx.save();
      ctx.setLineDash([10, 10]);
      ctx.strokeRect(0, 0, 100, 100);
      ctx.restore();
    },
  },
  path2d: {
    config: {
      fillStyle: {
        color: randomColor(),
      },
    },
    draw() {
      const { ctx, config } = this;
      ctx.fillStyle = config.fillStyle.color;
      const path2d = new Path2D();
      path2d.moveTo(200, 10);
      path2d.lineTo(300, 210);
      path2d.lineTo(100, 210);
      ctx.fill(path2d);
    },
  },
  roundRect: {
    config: {
      color: {
        color: randomColor(),
      },
      radius: {
        value: 5,
        min: 0,
        max: 20,
        step: 1,
      },
    },
    draw() {
      function roundedRect(ctx, x, y, width, height, radius) {
        ctx.beginPath();
        ctx.moveTo(x, y + radius);
        ctx.lineTo(x, y + height - radius);
        ctx.arcTo(x, y + height, x + radius, y + height, radius);
        ctx.lineTo(x + width - radius, y + height);
        ctx.arcTo(x + width, y + height, x + width, y + height - radius, radius);
        ctx.lineTo(x + width, y + radius);
        ctx.arcTo(x + width, y, x + width - radius, y, radius);
        ctx.lineTo(x + radius, y);
        ctx.arcTo(x, y, x, y + radius, radius);
        ctx.stroke();
      }
      const { ctx, config } = this;
      ctx.strokeStyle = config.color.color;
      ctx.lineWidth = 2;
      roundedRect(ctx, 50, 50, 250, 150, config.radius.value);
    },
  },
};

var Text = {
  text: {
    config: {
      x: {
        value: 10,
        min: 0,
        max: 500,
        step: 10,
      },
      y: {
        value: 50,
        min: 0,
        max: 500,
        step: 10,
      },
      font: '50px Arial',
      fillStyle: {
        color: randomColor(),
      },
      strokeStyle: {
        color: randomColor(),
      },
      textAlign: {
        value: 'left',
        values: [
          'start',
          'end',
          'left',
          'right',
          'center',
        ],
      },
      textBaseline: {
        value: 'top',
        values: [
          'top',
          'hanging',
          'middle',
          'alphabetic',
          'ideographic',
          'bottom',
        ],
      },
      text: 'AfghijpqyZ',
    },
    draw() {
      const { ctx, config } = this;
      ctx.font = config.font;
      ctx.fillStyle = config.fillStyle.color;
      ctx.strokeStyle = config.strokeStyle.color;
      ctx.textAlign = config.textAlign.value;
      ctx.textBaseline = config.textBaseline.value;
      const size = ctx.measureText(config.text);
      const height = ctx.measureText('e').width * 2;

      // text, x, y, maxWidth
      ctx.fillText(config.text, config.x.value, config.y.value);
      ctx.strokeRect(config.x.value, config.y.value, size.width, height);

      ctx.strokeText(config.text, config.x.value, config.y.value + 100);
      ctx.strokeRect(config.x.value, config.y.value + 100, size.width, height);

      // text base point
      ctx.fillStyle = 'red';
      ctx.beginPath();
      ctx.arc(config.x.value, config.y.value, 2, 0, Math.PI * 2);
      ctx.fill();
    },
  },
};

var Other = {
  transformations: {
    config: {
      x: {
        value: 100,
        min: -500,
        max: 500,
        step: 1,
      },
      y: {
        value: 100,
        min: -500,
        max: 500,
        step: 1,
      },
      sx1: {
        value: 1.5,
        min: -5,
        max: 5,
        step: 0.1,
      },
      sy1: {
        value: 1,
        min: -5,
        max: 5,
        step: 0.1,
      },
      sx2: {
        value: 1,
        min: -5,
        max: 5,
        step: 0.1,
      },
      sy2: {
        value: 1,
        min: -5,
        max: 5,
        step: 0.1,
      },
      rotation: {
        value: 25,
        min: -360,
        max: 360,
        step: 1,
      },
      shadowColor: {
        color: randomColor(),
      },
      shadowOffsetX: {
        value: 7,
        min: -30,
        max: 30,
        step: 1,
      },
      shadowOffsetY: {
        value: 15,
        min: -30,
        max: 30,
        step: 1,
      },
      shadowBlur: {
        value: 15,
        min: 0,
        max: 100,
        step: 1,
      },
      lineWidth: {
        value: 5,
        min: 0,
        max: 10,
        step: 1,
      },
      strokeStyle: {
        color: randomColor(),
      },
    },
    draw() {
      const { ctx, config } = this;
      const {
        x, y, sx1, sy1, sx2, sy2, rotation,
      } = config;
      ctx.strokeStyle = config.strokeStyle.color;
      ctx.lineWidth = config.lineWidth.value;
      ctx.shadowColor = config.shadowColor.color;
      ctx.shadowOffsetX = config.shadowOffsetX.value;
      ctx.shadowOffsetY = config.shadowOffsetY.value;
      ctx.shadowBlur = config.shadowBlur.value;
      ctx.translate(x.value, y.value);
      ctx.scale(sx1.value, sy1.value);
      ctx.scale(sx2.value, sy2.value);
      ctx.rotate(toRadians(rotation.value));
      ctx.beginPath();
      ctx.moveTo(30, 0);
      ctx.lineTo(0, 0);
      ctx.lineTo(0, 50);
      ctx.moveTo(0, 20);
      ctx.lineTo(20, 20);
      ctx.stroke();
    },
  },
  linearGradient: {
    config: {
      x0: {
        value: 100,
        min: 0,
        max: 500,
        step: 1,
      },
      y0: {
        value: 100,
        min: 0,
        max: 500,
        step: 1,
      },
      x1: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      y1: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      color1: {
        color: randomColor(),
      },
      color2: {
        color: randomColor(),
      },
    },
    draw() {
      const { ctx, config } = this;
      const {
        x0, y0, x1, y1,
      } = config;
      // x0, y0, x1, y1
      const linearGradient = ctx.createLinearGradient(x0.value, y0.value, x1.value, y1.value);
      linearGradient.addColorStop(0, config.color1.color);
      linearGradient.addColorStop(1, config.color2.color);
      ctx.fillStyle = linearGradient;
      ctx.beginPath();
      ctx.rect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
      ctx.fill();
      ctx.strokeRect(x0.value, y0.value, x1.value - x0.value, y1.value - y0.value);
    },
  },
  radialGradient: {
    config: {
      x0: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      y0: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      r0: {
        value: 0,
        min: 0,
        max: 500,
        step: 1,
      },
      x1: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      y1: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      r1: {
        value: 200,
        min: 0,
        max: 500,
        step: 1,
      },
      color1: {
        color: randomColor(),
      },
      color2: {
        color: randomColor(),
      },
    },
    draw() {
      const { ctx, config } = this;
      const {
        x0, y0, r0, x1, y1, r1,
      } = config;
      // x0, y0, r0, x1, y1, r1
      const radialGradient = ctx.createRadialGradient(x0.value, y0.value, r0.value,
        x1.value, y1.value, r1.value);
      radialGradient.addColorStop(0, config.color1.color);
      radialGradient.addColorStop(1, config.color2.color);
      ctx.fillStyle = radialGradient;
      ctx.beginPath();
      ctx.rect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
      ctx.fill();
      ctx.strokeRect(x1.value - r1.value, y1.value - r1.value, r1.value * 2, r1.value * 2);
    },
  },
  pattern: {
    config: {
      color: {
        color: randomColor(),
      },
      x: {
        value: 0,
        min: 0,
        max: 400,
        step: 1,
      },
      y: {
        value: 0,
        min: 0,
        max: 400,
        step: 1,
      },
      w: {
        value: 400,
        min: 0,
        max: 400,
        step: 1,
      },
      h: {
        value: 200,
        min: 0,
        max: 400,
        step: 1,
      },
      repetition: {
        value: 'repeat',
        values: [
          'repeat',
          'repeat-x',
          'repeat-y',
          'no-repeat',
        ],
      },
      useCanvas: false,
      imageBitmap: true,
      toDataURL() {
        const a = document.createElement('a');
        a.href = this.canvas.toDataURL();
        a.download = 'canvas.png';
        a.click();
      },
      toBlob() {
        this.canvas.toBlob((blob) => {
          const a = document.createElement('a');
          const url = URL.createObjectURL(blob);
          a.href = url;
          a.download = 'canvas.png';
          a.click();
          URL.revokeObjectURL(url);
        });
      },
    },
    before() {
      this.config.canvas = this.ctx.canvas;
    },
    async draw() {
      const { ctx, config } = this;
      const {
        x, y, w, h,
      } = config;
      let image;
      if (config.useCanvas) {
        const textureCanvas = document.createElement('canvas');
        textureCanvas.width = 40;
        textureCanvas.height = 40;
        const textureCtx = textureCanvas.getContext('2d');
        textureCtx.save();
        textureCtx.lineWidth = 1;
        textureCtx.strokeStyle = config.color.color;
        textureCtx.beginPath();
        textureCtx.moveTo(30, 10);
        textureCtx.lineTo(10, 30);
        textureCtx.moveTo(10, 10);
        textureCtx.lineTo(30, 30);
        textureCtx.stroke();
        textureCtx.restore();
        image = textureCanvas;
      } else if (config.imageBitmap) {
        image = await loadImageBitmap('./images/Canvas_createpattern.png');
      } else {
        image = await loadImage('./images/Canvas_createpattern.png');
      }
      const pattern = ctx.createPattern(image, config.repetition.value);
      ctx.fillStyle = pattern;
      ctx.beginPath();
      ctx.rect(x.value, y.value, w.value, h.value);
      ctx.fill();
      ctx.strokeRect(x.value, y.value, w.value, h.value);
    },
  },
  drawImage: {
    config: {
      sx: {
        value: 0,
        min: 0,
        max: 400,
        step: 1,
      },
      sy: {
        value: 0,
        min: 0,
        max: 400,
        step: 1,
      },
      sw: {
        value: 86,
        min: 0,
        max: 400,
        step: 1,
      },
      sh: {
        value: 117,
        min: 0,
        max: 400,
        step: 1,
      },
      dx: {
        value: 0,
        min: 0,
        max: 400,
        step: 1,
      },
      dy: {
        value: 0,
        min: 0,
        max: 400,
        step: 1,
      },
      dw: {
        value: 86,
        min: 0,
        max: 400,
        step: 1,
      },
      dh: {
        value: 117,
        min: 0,
        max: 400,
        step: 1,
      },
    },
    async draw() {
      const { ctx, config } = this;
      const {
        sx, sy, sw, sh, dx, dy, dw, dh,
      } = config;
      let { image, color } = this;
      if (!image) {
        image = await loadImageBitmap('./images/Canvas_createpattern.png');
        this.image = image;
      }
      if (!color) {
        color = randomColor();
        this.color = color;
      }
      ctx.fillStyle = color;
      ctx.font = '20px Arial';
      // image, dx, dy
      ctx.drawImage(image, dx.value, dy.value);
      // image, dx, dy, dw, dh
      ctx.drawImage(image, dx.value + 150, dy.value, dw.value, dh.value);
      // image, sx, sy, sw, sh, dx, dy, dw, dh
      ctx.drawImage(image, sx.value, sy.value, sw.value, sh.value,
        dx.value + 300, dy.value, dw.value, dh.value);
      ctx.fillText('(image, dx, dy)', dx.value, dy.value + 130);
      ctx.fillText('(image, dx, dy, dw, dh)', dx.value + 150, dy.value + 150);
      ctx.textAlign = 'right';
      ctx.fillText('(image, sx, sy, sw, sh, dx, dy, dw, dh)', dx.value + 300 + 86, dy.value + 170);
    },
  },
  globalAlpha: {
    config: {
      color: {
        color: randomColor(),
      },
      globalAlpha: {
        value: 1,
        min: 0,
        max: 1,
        step: 0.1,
      },
      alpha: {
        value: 0.5,
        min: 0,
        max: 1,
        step: 0.1,
      },
    },
    async draw() {
      const { ctx, config } = this;
      ctx.globalAlpha = config.globalAlpha.value;
      ctx.rect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
      let { image } = this;
      if (!image) {
        image = await loadImageBitmap('./images/Canvas_createpattern.png');
        this.image = image;
      }
      ctx.drawImage(this.image, 0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
      const color = getColorValue(config.color.color);
      color.a = config.alpha.value;
      ctx.fillStyle = color;
      ctx.beginPath();
      ctx.arc(100, 100, 50, 0, Math.PI * 2);
      ctx.fill();
    },
  },
  imageData: {
    config: {
      color: {
        color: randomColor(),
      },
    },
    async draw() {
      const { ctx, config } = this;
      let { image } = this;
      if (!image) {
        image = await loadImageBitmap('./images/Canvas_createpattern.png');
        this.image = image;
      }
      image = getColorImage(image, config.color.color);
      ctx.drawImage(image, 50, 50, image.width, image.height);
    },
  },
  svg: {
    config: {
      scale: {
        value: 0.5,
        min: 0.1,
        max: 5,
        step: 0.1,
      },
    },
    async draw() {
      const { ctx, config } = this;
      ctx.translate(ctx.canvas.clientWidth / 2, ctx.canvas.clientHeight / 2);
      ctx.scale(config.scale.value, config.scale.value);
      let { image } = this;
      if (!image) {
        image = await loadImage('./images/save-money.svg');
        this.image = image;
      }
      ctx.drawImage(image, -image.width / 2, -image.height / 2, image.width, image.height);
    },
  },
  globalCompositeOperation: {
    config: {
      globalCompositeOperation: {
        value: 'source-over',
        values: [
          'source-over', 'source-in', 'source-out', 'source-atop',
          'destination-over', 'destination-in', 'destination-out', 'destination-atop',
          'lighter', 'copy', 'xor', 'multiply', 'screen', 'overlay', 'darken',
          'lighten', 'color-dodge', 'color-burn', 'hard-light', 'soft-light',
          'difference', 'exclusion', 'hue', 'saturation', 'color', 'luminosity',
        ],
      },
      color: {
        color: randomColor(),
      },
      alpha: {
        value: 0.5,
        min: 0,
        max: 1,
        step: 0.1,
      },
    },
    async draw() {
      const { ctx, config } = this;
      ctx.save();
      ctx.translate(ctx.canvas.clientWidth / 2, ctx.canvas.clientHeight / 2);
      ctx.scale(0.5, 0.5);
      let { image } = this;
      if (!image) {
        image = await loadImage('./images/save-money.svg');
        this.image = image;
      }
      ctx.drawImage(image, -image.width / 2, -image.height / 2, image.width, image.height);
      ctx.restore();
      ctx.save();
      ctx.globalCompositeOperation = config.globalCompositeOperation.value;
      const color = getColorValue(config.color.color);
      color.a = config.alpha.value;
      ctx.fillStyle = color;
      ctx.fillRect(100, 100, 200, 200);
      ctx.restore();
    },
  },
  animation: {
    clear: false,
    config: {
      radius: {
        value: 5,
        min: 0,
        max: 400,
        step: 1,
      },
      color: {
        color: randomColor(),
      },
    },
    clearCanvas() {
      const { ctx } = this;
      ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
    },
    before() {
      this.clearCanvas();
      this.handleMouseMove = (e) => {
        this.point = getPointAt(this.ctx.canvas, e);
      };
      this.ctx.canvas.addEventListener('mousemove', this.handleMouseMove);
    },
    after() {
      if (this.animation) {
        cancelAnimationFrame(this.animation);
      }
      this.clearCanvas();
      this.ctx.canvas.removeEventListener('mousemove', this.handleMouseMove);
    },
    draw() {
      const { ctx, config } = this;
      ctx.globalCompositeOperation = 'destination-out';
      ctx.fillStyle = 'rgba(0, 0, 0, 0.1)';
      ctx.fillRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);

      ctx.globalCompositeOperation = 'source-over';
      ctx.fillStyle = config.color.color;
      if (this.point) {
        ctx.beginPath();
        ctx.arc(this.point.x, this.point.y, config.radius.value, 0, Math.PI * 2);
        ctx.fill();
      }
      this.animation = requestAnimationFrame(() => {
        this.draw();
      });
    },
  },
  test: {
    config: {
      radius: {
        value: 50,
        min: 0,
        max: 400,
        step: 1,
      },
    },
    draw() {
      const { ctx, config } = this;
      ctx.beginPath();
      ctx.arc(100, 100, config.radius.value, 0, Math.PI * 2);
      ctx.stroke();
    },
  },
};

const modules = {
  Clear,
  Path,
  Text,
  Other,
};
const codes = {};

Object.keys(modules).forEach((moduleName) => {
  const module = modules[moduleName];
  Object.keys(module).forEach((methodName) => {
    const method = module[methodName];
    codes[`${moduleName}_${methodName}`] = method;
  });
});

const canvas = document.getElementById('canvas');
const lessons = document.getElementById('lessons');
const code = document.getElementById('code');
const ctx = canvas.getContext('2d');
window.ctx = ctx;
toHighDPI(canvas);

function addOption(lessonName) {
  const option = document.createElement('option');
  option.value = lessonName;
  option.innerHTML = lessonName;
  lessons.add(option);
}

let lastSelected;
async function run() {
  if (!lessons.value) {
    ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
    return;
  }
  if (lastSelected && lastSelected.gui) {
    lastSelected.gui.destroy();
  }
  if (lastSelected && lastSelected.after) {
    lastSelected.after();
  }
  const method = codes[lessons.value];
  lastSelected = method;
  if (method.clear !== false) {
    ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
    ctx.save();
  }
  if (method.draw) {
    method.ctx = ctx;
    if (method.config) {
      const gui = new GUI$1();
      method.gui = gui;
      Object.keys(method.config).forEach((key) => {
        let controller;
        const value = method.config[key];
        if (value.min || value.max) {
          controller = gui.add(value, 'value', value.min, value.max, value.step);
        } else if (value.values) {
          controller = gui.add(value, 'value', value.values);
        } else if (value.color) {
          controller = gui.addColor(value, 'color');
        } else {
          controller = gui.add(method.config, key);
        }
        controller.name(key);
        if (!(method.config[key] instanceof Function)) {
          controller.onChange(() => {
            ctx.clearRect(0, 0, ctx.canvas.clientWidth, ctx.canvas.clientHeight);
            ctx.save();
            method.draw();
            ctx.restore();
          });
        }
      });
    }
    await method.draw();
  } else {
    await method(ctx);
  }
  if (method.clear !== false) {
    ctx.restore();
  }
  if (method.before) {
    method.before();
  }
}

addOption('');
Object.keys(codes).forEach((lessonName) => {
  addOption(lessonName);
});

function setLesson(lesson) {
  lessons.value = lesson;
  canvas._lesson = lesson;
  if (!lessons.value) {
    code.value = '';
    document.title = 'Canvas lessions';
  } else {
    const method = codes[lessons.value];
    code.value = method.draw || method;
    document.title = `Canvas lessions - ${lessons.value}`;
  }
  run();
}

// https://caniuse.com/#search=URLSearchParams
// https://davidwalsh.name/query-string-javascript
// https://usefulangle.com/post/81/javascript-change-url-parameters
// https://github.com/ungap/url-search-params
const urlParams = new URLSearchParams(window.location.search);
const initLession = urlParams.get('lesson') || '';
if (initLession) {
  setLesson(initLession);
}

lessons.addEventListener('change', () => {
  // https://developer.mozilla.org/en-US/docs/Web/API/History_API
  // https://caniuse.com/#search=history
  // https://css-tricks.com/using-the-html5-history-api/
  urlParams.set('lesson', lessons.value);
  window.history.pushState({ value: lessons.value }, null, `?${urlParams.toString()}`);
  setLesson(lessons.value);
});

window.addEventListener('popstate', (e) => {
  const lesson = (e.state && e.state.value) || initLession;
  setLesson(lesson);
});
